From 5ccb0e92faf90e1e9125da434b9c37e85d0aaf28 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Thu, 20 Oct 2016 12:24:02 +0100 Subject: ash: return exit status of nofork applets The commit 'ash: eval: Return status in eval functions' changed how exit status is handled in eval functions. The case of nofork applets was missed, resulting in the incorrect status potentially being returned for nofork applets when FEATURE_SH_NOFORK is enabled. Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index 50f479d1a..c9d39b875 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9576,7 +9576,7 @@ evalcommand(union node *cmd, int flags) if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { listsetvar(varlist.list, VEXPORT|VSTACK); /* run _main() */ - exitstatus = run_nofork_applet(applet_no, argv); + status = run_nofork_applet(applet_no, argv); break; } #endif -- cgit v1.2.3-55-g6feb From 1336052a490ffe7a3790ee3c1366756ca8a7f5ca Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Oct 2016 01:25:05 +0200 Subject: lineedit: fix completion with applet names. closes 9361 Patch by Ron Yorston Signed-off-by: Denys Vlasenko --- libbb/lineedit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbb/lineedit.c b/libbb/lineedit.c index 3bfff0084..2cc61db40 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -782,7 +782,7 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) pf_len = strlen(pfind); #if ENABLE_FEATURE_SH_STANDALONE && NUM_APPLETS != 1 - if (type == FIND_EXE_ONLY) { + if (type == FIND_EXE_ONLY && !dirbuf) { const char *p = applet_names; while (*p) { -- cgit v1.2.3-55-g6feb From 61d5997b586a28f79d54b28ce6963b643dff6e9e Mon Sep 17 00:00:00 2001 From: Brian Foley Date: Sat, 15 Oct 2016 14:45:40 +0100 Subject: awk: fix segfault on for loop syntax error Parsing "for()" segfaults as awk fails to find loop iteration expressions. Signed-off-by: Brian Foley Signed-off-by: Denys Vlasenko --- editors/awk.c | 2 +- testsuite/awk.tests | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/editors/awk.c b/editors/awk.c index d0269b9f4..685e8bed8 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -1514,7 +1514,7 @@ static void chain_group(void) next_token(TC_SEQSTART); n2 = parse_expr(TC_SEMICOL | TC_SEQTERM); if (t_tclass & TC_SEQTERM) { /* for-in */ - if ((n2->info & OPCLSMASK) != OC_IN) + if (!n2 || (n2->info & OPCLSMASK) != OC_IN) syntax_error(EMSG_UNEXP_TOKEN); n = chain_node(OC_WALKINIT | VV); n->l.n = n2->l.n; diff --git a/testsuite/awk.tests b/testsuite/awk.tests index adab4ae1e..82937bc10 100755 --- a/testsuite/awk.tests +++ b/testsuite/awk.tests @@ -316,6 +316,9 @@ testing "awk continue" \ "" \ 'BEGIN { if (1) continue; else a = 1 }' +testing "awk handles invalid for loop" \ + "awk '{ for() }' 2>&1" "awk: cmd. line:1: Unexpected token\n" "" "" + # testing "description" "command" "result" "infile" "stdin" exit $FAILCOUNT -- cgit v1.2.3-55-g6feb From db74c6caedf752623820cd09f7fbeaf4152e1de8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Oct 2016 21:12:33 +0200 Subject: ash: explain EXP_REDIR and why we (dont) glob redir filenames Signed-off-by: Denys Vlasenko --- shell/ash.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/shell/ash.c b/shell/ash.c index c9d39b875..d9595bb8f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5545,6 +5545,17 @@ ash_arith(const char *s) #define EXP_TILDE 0x2 /* do normal tilde expansion */ #define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ #define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +/* ^^^^^^^^^^^^^^ this is meant to support constructs such as "cmd >file*.txt" + * POSIX says for this case: + * Pathname expansion shall not be performed on the word by a + * non-interactive shell; an interactive shell may perform it, but shall + * do so only when the expansion would result in one word. + * Currently, our code complies to the above rule by never globbing + * redirection filenames. + * Bash performs globbing, unless it is non-interactive and in POSIX mode. + * (this means that on a typical Linux distro, bash almost always + * performs globbing, and thus diverges from what we do). + */ #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ #define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ #define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -- cgit v1.2.3-55-g6feb From 69312e87b008363575a6d6603f54f94d8150e1cc Mon Sep 17 00:00:00 2001 From: Tito Ragusa Date: Mon, 24 Oct 2016 21:52:10 +0200 Subject: strings: implement -t radix v2: minor code cleanup, no changes. v1: Implement -t radix option. Fix help text for -o option. Signed-off-by: Tito Ragusa Signed-off-by: Denys Vlasenko --- miscutils/strings.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/miscutils/strings.c b/miscutils/strings.c index 9f5018244..ee6649625 100644 --- a/miscutils/strings.c +++ b/miscutils/strings.c @@ -8,13 +8,16 @@ */ //usage:#define strings_trivial_usage -//usage: "[-afo] [-n LEN] [FILE]..." +//usage: "[-fo] [-t o/d/x] [-n LEN] [FILE]..." //usage:#define strings_full_usage "\n\n" //usage: "Display printable strings in a binary file\n" -//usage: "\n -a Scan whole file (default)" -//usage: "\n -f Precede strings with filenames" -//usage: "\n -n LEN At least LEN characters form a string (default 4)" -//usage: "\n -o Precede strings with decimal offsets" +//We usually don't bother user with "nop" options. They work, but are not shown: +////usage: "\n -a Scan whole file (default)" +//unimplemented alternative is -d: Only strings from initialized, loaded data sections +//usage: "\n -f Precede strings with filenames" +//usage: "\n -o Precede strings with octal offsets" +//usage: "\n -t o/d/x Precede strings with offsets in base 8/10/16" +//usage: "\n -n LEN At least LEN characters form a string (default 4)" #include "libbb.h" @@ -22,6 +25,7 @@ #define PRINT_NAME 2 #define PRINT_OFFSET 4 #define SIZE 8 +#define PRINT_RADIX 16 int strings_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int strings_main(int argc UNUSED_PARAM, char **argv) @@ -33,8 +37,11 @@ int strings_main(int argc UNUSED_PARAM, char **argv) char *string; const char *fmt = "%s: "; const char *n_arg = "4"; + /* default for -o */ + const char *radix = "o"; + char *radix_fmt; - getopt32(argv, "afon:", &n_arg); + getopt32(argv, "afon:t:", &n_arg, &radix); /* -a is our default behaviour */ /*argc -= optind;*/ argv += optind; @@ -43,6 +50,11 @@ int strings_main(int argc UNUSED_PARAM, char **argv) string = xzalloc(n + 1); n--; + if ((radix[0] != 'd' && radix[0] != 'o' && radix[0] != 'x') || radix[1] != 0) + bb_show_usage(); + + radix_fmt = xasprintf("%%7"OFF_FMT"%s ", radix); + if (!*argv) { fmt = "{%s}: "; *--argv = (char *)bb_msg_standard_input; @@ -67,8 +79,8 @@ int strings_main(int argc UNUSED_PARAM, char **argv) if (option_mask32 & PRINT_NAME) { printf(fmt, *argv); } - if (option_mask32 & PRINT_OFFSET) { - printf("%7"OFF_FMT"o ", offset - n); + if (option_mask32 & (PRINT_OFFSET | PRINT_RADIX)) { + printf(radix_fmt, offset - n); } fputs(string, stdout); } @@ -85,8 +97,10 @@ int strings_main(int argc UNUSED_PARAM, char **argv) fclose_if_not_stdin(file); } while (*++argv); - if (ENABLE_FEATURE_CLEAN_UP) + if (ENABLE_FEATURE_CLEAN_UP) { free(string); + free(radix_fmt); + } fflush_stdout_and_exit(status); } -- cgit v1.2.3-55-g6feb From f9beeb22e2a4128ed85a8dc267d0823e5cfd3f47 Mon Sep 17 00:00:00 2001 From: Brian Foley Date: Tue, 25 Oct 2016 14:20:55 +0200 Subject: udhcpc: check read of option length byte to be within packet function old new delta udhcp_get_option 215 220 +5 udhcp_run_script 802 803 +1 Signed-off-by: Brian Foley Signed-off-by: Denys Vlasenko --- networking/udhcp/common.c | 8 +++++++- networking/udhcp/dhcpc.c | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c index 0cf4dab63..589bcd674 100644 --- a/networking/udhcp/common.c +++ b/networking/udhcp/common.c @@ -226,9 +226,12 @@ uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code) rem = sizeof(packet->options); while (1) { if (rem <= 0) { + complain: bb_error_msg("bad packet, malformed option field"); return NULL; } + + /* DHCP_PADDING and DHCP_END have no [len] byte */ if (optionptr[OPT_CODE] == DHCP_PADDING) { rem--; optionptr++; @@ -251,10 +254,13 @@ uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code) } break; } + + if (rem <= OPT_LEN) + goto complain; /* complain and return NULL */ len = 2 + optionptr[OPT_LEN]; rem -= len; if (rem < 0) - continue; /* complain and return NULL */ + goto complain; /* complain and return NULL */ if (optionptr[OPT_CODE] == code) { log_option("option found", optionptr); diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c index bef73277a..1c1051107 100644 --- a/networking/udhcp/dhcpc.c +++ b/networking/udhcp/dhcpc.c @@ -450,7 +450,7 @@ static char **fill_envp(struct dhcp_packet *packet) temp = udhcp_get_option(packet, i); if (temp) { if (i == DHCP_OPTION_OVERLOAD) - overload = *temp; + overload |= *temp; else if (i == DHCP_SUBNET) envc++; /* for $mask */ envc++; @@ -476,7 +476,7 @@ static char **fill_envp(struct dhcp_packet *packet) * uint16_t secs; // elapsed since client began acquisition/renewal * uint16_t flags; // only one flag so far: bcast. Never set by server * uint32_t ciaddr; // client IP (usually == yiaddr. can it be different - * // if during renew server wants to give us differn IP?) + * // if during renew server wants to give us different IP?) * uint32_t gateway_nip; // relay agent IP address * uint8_t chaddr[16]; // link-layer client hardware address (MAC) * TODO: export gateway_nip as $giaddr? -- cgit v1.2.3-55-g6feb From f11c6989ba40f6bed8c4b4f0b6b7578aba3a63f9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 14:25:52 +0200 Subject: udhcpc: check read of overload option data byte to be within packet function old new delta udhcp_get_option 220 225 +5 Signed-off-by: Denys Vlasenko --- networking/udhcp/common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c index 589bcd674..1aaf5255c 100644 --- a/networking/udhcp/common.c +++ b/networking/udhcp/common.c @@ -268,7 +268,8 @@ uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code) } if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) { - overload |= optionptr[OPT_DATA]; + if (len >= 3) + overload |= optionptr[OPT_DATA]; /* fall through */ } optionptr += len; -- cgit v1.2.3-55-g6feb From b7adf7ac32e65906932935daf3d7a02d94818e09 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 17:00:13 +0200 Subject: ash,hush: set exit code 127 in "sh /does/not/exist" case Upstream commit 1 for ash: [ERROR] Allow the originator of EXERROR to set the exit status Some errors have exit status values specified by POSIX and it is therefore desirable to be able to set the exit status at the EXERROR source rather than in main.c. Signed-off-by: Herbert Xu Upstream commit 2 for ash: [INPUT] Use exit status 127 when the script to run does not exist This commit makes dash exit with return code 127 instead of 2 if started as non-interactive shell with a non-existent command_file specified as argument (or a directory), as documented in http://www.opengroup.org/onlinepubs/009695399/utilities/sh.html#tag_04_128_14 The wrong exit code was reported by Clint Adams and Jari Aalto through http://bugs.debian.org/548743 http://bugs.debian.org/548687 Signed-off-by: Gerrit Pape Signed-off-by: Herbert Xu NB: in fact, http://bugs.debian.org/548687 was not fixed by this: "sh /dir/" thinks that EISDIR error on read is EOF, and exits 0. Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 +-- shell/hush.c | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index d9595bb8f..9798a9675 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -10234,6 +10234,7 @@ setinputfile(const char *fname, int flags) if (fd < 0) { if (flags & INPUT_NOFILE_OK) goto out; + exitstatus = 127; ash_msg_and_raise_error("can't open '%s'", fname); } if (fd < 10) { @@ -13403,8 +13404,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) reset(); e = exception_type; - if (e == EXERROR) - exitstatus = 2; s = state; if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) { exitshell(); diff --git a/shell/hush.c b/shell/hush.c index 9b51f389e..d7a0d761e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8409,7 +8409,9 @@ int hush_main(int argc, char **argv) G.global_argc--; G.global_argv++; debug_printf("running script '%s'\n", G.global_argv[0]); + xfunc_error_retval = 127; /* for "hush /does/not/exist" case */ input = xfopen_for_read(G.global_argv[0]); + xfunc_error_retval = 1; remember_FILE(input); install_special_sighandlers(); parse_and_run_file(input); -- cgit v1.2.3-55-g6feb From 061a09091f21f8e2d3ac61cb1a5f8c919ddce26e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 17:24:25 +0200 Subject: ash: [BUILTIN] Use EXEXIT in place of EXEXEC Upstream commit: Date: Sun, 28 Nov 2010 20:44:37 +0800 [BUILTIN] Use EXEXIT in place of EXEXEC The intended semantics of EXEXEC are identical to EXEXIT, so simplify by using EXEXIT directly. Functional change: in edge cases (exec within a trap handler), this causes the exit status from exec not to be clobbered. For example, without this patch: $ sh -c 'trap "exec nonexistent" EXIT'; echo $? exec: 1: nonexistent: not found 0 And with it: $ sh -c 'trap "exec nonexistent" EXIT'; echo $? exec: 1: nonexistent: not found 127 Signed-off-by: Jonathan Nieder Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 9798a9675..29d1d57ca 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -302,7 +302,6 @@ struct globals_misc { #define EXINT 0 /* SIGINT received */ #define EXERROR 1 /* a generic error */ #define EXSHELLPROC 2 /* execute a shell procedure */ -#define EXEXEC 3 /* command execution failed */ #define EXEXIT 4 /* exit the shell */ #define EXSIG 5 /* trapped signal in wait(1) */ @@ -7618,7 +7617,7 @@ shellexec(char **argv, const char *path, int idx) exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", argv[0], e, suppress_int)); - ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); + ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found")); /* NOTREACHED */ } @@ -9635,7 +9634,7 @@ evalcommand(union node *cmd, int flags) if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { int exit_status; int i = exception_type; - if (i == EXEXIT || i == EXEXEC) + if (i == EXEXIT) goto raise; exit_status = 2; if (i == EXINT) -- cgit v1.2.3-55-g6feb From 1825e4f935f8e9ef9685766ab60659d78602c906 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 17:26:56 +0200 Subject: ash: remove unused EXSHELLPROC Upstream commit: Date: Sun, 28 Nov 2010 20:47:07 +0800 [BUILTIN] Stop documenting EXSHELLPROC At some point between ash 0.3.5-11.0.1 and ash 0.3.8-37, Debian ash stopped using the EXSHELLPROC exception to handle shell scripts without a magic number. Remove all remaining references to it to avoid confusion. Signed-off-by: Jonathan Nieder Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index 29d1d57ca..1511d95c7 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -301,7 +301,6 @@ struct globals_misc { /* exceptions */ #define EXINT 0 /* SIGINT received */ #define EXERROR 1 /* a generic error */ -#define EXSHELLPROC 2 /* execute a shell procedure */ #define EXEXIT 4 /* exit the shell */ #define EXSIG 5 /* trapped signal in wait(1) */ -- cgit v1.2.3-55-g6feb From 6a94cee409e4e2f4394d022703bfbe402c3cdee5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 17:40:25 +0200 Subject: ash: reduce code differences from upstream Upstream commit: Date: Wed, 8 Sep 2010 16:21:52 +0800 [JOBS] Debug compile fix No point in tracing a no longer undeclared "ps->cmd", fixes: jobs.c: In function \u2018commandtext\u2019: jobs.c:1192: error: \u2018ps\u2019 undeclared (first use in this function) jobs.c:1192: error: (Each undeclared identifier is reported only once jobs.c:1192: error: for each function it appears in.) Signed-off-by: maximilian attems Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 1511d95c7..cf48b7743 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -4668,8 +4668,7 @@ commandtext(union node *n) STARTSTACKSTR(cmdnextc); cmdtxt(n); name = stackblock(); - TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", - name, cmdnextc, cmdnextc)); + TRACE(("commandtext: name %p, end %p\n", name, cmdnextc)); return ckstrdup(name); } #endif /* JOBS */ -- cgit v1.2.3-55-g6feb From 960ca385b721400082323da7f8cddb7f3cfb3027 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 18:12:15 +0200 Subject: ash: add comment explaining "set -e; $(cmd)" discrepancy Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shell/ash.c b/shell/ash.c index cf48b7743..79439bf3e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5924,6 +5924,14 @@ evalbackcmd(union node *n, struct backcmd *result) copyfd(pip[1], 1 | COPYFD_EXACT); close(pip[1]); } +/* TODO: eflag clearing makes the following not abort: + * ash -c 'set -e; z=$(false;echo foo); echo $z' + * which is what bash does (unless it is in POSIX mode). + * dash deleted "eflag = 0" line in the commit + * Date: Mon, 28 Jun 2010 17:11:58 +1000 + * [EVAL] Don't clear eflag in evalbackcmd + * For now, preserve bash-like behavior, it seems to be somewhat more useful: + */ eflag = 0; evaltree(n, EV_EXIT); /* actually evaltreenr... */ /* NOTREACHED */ -- cgit v1.2.3-55-g6feb From cf98b0c0857f4e83caa1c8aca95ad73d9b723e2f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 18:19:39 +0200 Subject: ash: [EVAL] Check exit for eval NSUBSHELL Upstream commit: Date: Tue, 6 Jul 2010 17:50:37 +0800 [PATCH 161/277] [EVAL] Check exit for eval NSUBSHELL Example: $ dash -c 'set -e; (false); echo here' here With this commit, dash exits 1 before echo. The bug was reported by Stefan Fritsch through http://bugs.debian.org/514863 Signed-off-by: Gerrit Pape Signed-off-by: Herbert Xu This was fixed differently in our tree: Date: Fri Sep 16 19:04:02 2016 +0000 ash: exit after subshell error when errexit option is set When "set -e" option is on, shell must exit when any command fails, including compound commands of the form (compound-list) executed in a subshell. Bash and dash shells have this behaviour. Also add a corresponding testcase. Signed-off-by: Rostislav Skudnov Signed-off-by: Denys Vlasenko Signed-off-by: Denys Vlasenko --- shell/ash.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 79439bf3e..9dcf80975 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -8610,11 +8610,9 @@ evaltree(union node *n, int flags) evalfn = evalloop; goto calleval; case NSUBSHELL: - evalfn = evalsubshell; - goto checkexit; case NBACKGND: evalfn = evalsubshell; - goto calleval; + goto checkexit; case NPIPE: evalfn = evalpipe; goto checkexit; -- cgit v1.2.3-55-g6feb From 20a2cd6291924d405e636916962c6294d3b8544e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 19:04:39 +0200 Subject: ash: [REDIR] Remove EMFILE special case Upstream commit: Date: Sun, 6 May 2007 12:01:37 +1000 [REDIR] Remove EMFILE special case No caller of copyfd need to ignore EMFILE so we can remove the special case and just let it call sh_error on any error. Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 9dcf80975..8092e3971 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5190,9 +5190,7 @@ openredirect(union node *redir) } /* - * Copy a file descriptor to be >= to. Returns -1 - * if the source file descriptor is closed, EMPTY if there are no unused - * file descriptors left. + * Copy a file descriptor to be >= to. Throws exception on error. */ /* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD). * old code was doing close(to) prior to copyfd() to achieve the same */ @@ -5213,8 +5211,6 @@ copyfd(int from, int to) newfd = fcntl(from, F_DUPFD, to); } if (newfd < 0) { - if (errno == EMFILE) - return EMPTY; /* Happens when source fd is not open: try "echo >&99" */ ash_msg_and_raise_error("%d: %m", from); } @@ -10243,8 +10239,6 @@ setinputfile(const char *fname, int flags) if (fd < 10) { fd2 = copyfd(fd, 10); close(fd); - if (fd2 < 0) - ash_msg_and_raise_error("out of file descriptors"); fd = fd2; } setinputfd(fd, flags & INPUT_PUSH_FILE); -- cgit v1.2.3-55-g6feb From 7aec86820d7bcf2842d167969c4fa7159ea643f7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 20:26:02 +0200 Subject: ash: [EVAL] Let funcnode refer to a function definition, not its first command Upstream patch: Date: Tue, 15 Mar 2011 15:44:47 +0800 [EVAL] Let funcnode refer to a function definition, not its first command It is not unrelated: I changed the meaning of struct funcnode's field n to refer to the function definition, rather than the list of the function's commands, because I needed to refer to the function definition node from evalfun, which only gets passed a funcnode. But it is something that could be applied independently (without being useful by itself), so I've attached it as a separate patch for easier review. Signed-off-by: Harald van Dijk Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 8092e3971..b7f20ba36 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -8449,14 +8449,14 @@ copyfunc(union node *n) * Define a shell function. */ static void -defun(char *name, union node *func) +defun(union node *func) { struct cmdentry entry; INT_OFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); - addcmdentry(name, &entry); + addcmdentry(func->narg.text, &entry); INT_ON; } @@ -8654,7 +8654,7 @@ evaltree(union node *n, int flags) status = 0; goto setstatus; case NDEFUN: - defun(n->narg.text, n->narg.next); + defun(n); /* Not necessary. To test it: * "false; f() { qwerty; }; echo $?" should print 0. */ @@ -9079,7 +9079,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam.optind = 1; shellparam.optoff = -1; #endif - evaltree(&func->n, flags & EV_TESTED); + evaltree(func->n.narg.next, flags & EV_TESTED); funcdone: INT_OFF; funcnest--; -- cgit v1.2.3-55-g6feb From caee80cd3da65670a576e610044fdc5e7c957d6d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 20:49:53 +0200 Subject: ash: [SHELL] Move flushall to the point just before _exit Upstream commit: We need to flush at the very end in case we've generated any errors before that. The flushall call cannot perform a longjmp so it's safe there. Date: Sat, 22 Sep 2007 20:50:21 +0800 [SHELL] Move flushall to the point just before _exit We need to flush at the very end in case we've generated any errors before that. The flushall call cannot perform a longjmp so it's safe there. Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index b7f20ba36..3f1405d81 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13187,12 +13187,12 @@ exitshell(void) evalstring(p, 0); /*free(p); - we'll exit soon */ } - flush_stdout_stderr(); out: /* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}". * our setjobctl(0) does not panic if tcsetpgrp fails inside it. */ setjobctl(0); + flush_stdout_stderr(); _exit(status); /* NOTREACHED */ } -- cgit v1.2.3-55-g6feb From 579ad107a69690efc17401c487b06e89d9e8a91d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 21:10:20 +0200 Subject: ash: [EXPAND] Removed herefd hack Upstream commit: Date: Sun, 11 Nov 2007 15:00:06 +0800 [EXPAND] Removed herefd hack The herefd hack goes back more than a decade. it limits the amount of memory we have to allocate when expanding here-documents by writing the result out from time to time. However, it's no longer safe because the stack is used to place intermediate results too and there we certainly don't want to write them out should we be short on memory. In any case, with today's computers we can afford to keep the entire result in memory and write them out at the end. function old new delta redirect 1268 1264 -4 ash_main 1485 1478 -7 subevalvar 1157 1132 -25 growstackstr 54 24 -30 argstr 1192 1154 -38 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/5 up/down: 0/-104) Total: -104 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 61 ++++++++++++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 3f1405d81..527abc7b1 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1399,7 +1399,6 @@ struct globals_memstack { char *g_stacknxt; // = stackbase.space; char *sstrend; // = stackbase.space + MINSIZE; size_t g_stacknleft; // = MINSIZE; - int herefd; // = -1; struct stack_block stackbase; }; extern struct globals_memstack *const ash_ptr_to_globals_memstack; @@ -1408,7 +1407,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; #define g_stacknxt (G_memstack.g_stacknxt ) #define sstrend (G_memstack.sstrend ) #define g_stacknleft (G_memstack.g_stacknleft) -#define herefd (G_memstack.herefd ) #define stackbase (G_memstack.stackbase ) #define INIT_G_memstack() do { \ (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \ @@ -1417,7 +1415,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; g_stacknxt = stackbase.space; \ g_stacknleft = MINSIZE; \ sstrend = stackbase.space + MINSIZE; \ - herefd = -1; \ } while (0) @@ -1605,10 +1602,6 @@ static void * growstackstr(void) { size_t len = stackblocksize(); - if (herefd >= 0 && len >= 1024) { - full_write(herefd, stackblock(), len); - return stackblock(); - } growstackblock(); return (char *)stackblock() + len; } @@ -5893,33 +5886,28 @@ static int evaltree(union node *, int); static void FAST_FUNC evalbackcmd(union node *n, struct backcmd *result) { - int saveherefd; + int pip[2]; + struct job *jp; result->fd = -1; result->buf = NULL; result->nleft = 0; result->jp = NULL; - if (n == NULL) + if (n == NULL) { goto out; + } - saveherefd = herefd; - herefd = -1; - - { - int pip[2]; - struct job *jp; - - if (pipe(pip) < 0) - ash_msg_and_raise_error("pipe call failed"); - jp = makejob(/*n,*/ 1); - if (forkshell(jp, n, FORK_NOJOB) == 0) { - FORCE_INT_ON; - close(pip[0]); - if (pip[1] != 1) { - /*close(1);*/ - copyfd(pip[1], 1 | COPYFD_EXACT); - close(pip[1]); - } + if (pipe(pip) < 0) + ash_msg_and_raise_error("pipe call failed"); + jp = makejob(/*n,*/ 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCE_INT_ON; + close(pip[0]); + if (pip[1] != 1) { + /*close(1);*/ + copyfd(pip[1], 1 | COPYFD_EXACT); + close(pip[1]); + } /* TODO: eflag clearing makes the following not abort: * ash -c 'set -e; z=$(false;echo foo); echo $z' * which is what bash does (unless it is in POSIX mode). @@ -5928,15 +5916,14 @@ evalbackcmd(union node *n, struct backcmd *result) * [EVAL] Don't clear eflag in evalbackcmd * For now, preserve bash-like behavior, it seems to be somewhat more useful: */ - eflag = 0; - evaltree(n, EV_EXIT); /* actually evaltreenr... */ - /* NOTREACHED */ - } - close(pip[1]); - result->fd = pip[0]; - result->jp = jp; + eflag = 0; + evaltree(n, EV_EXIT); /* actually evaltreenr... */ + /* NOTREACHED */ } - herefd = saveherefd; + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + out: TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", result->fd, result->buf, result->nleft, result->jp)); @@ -6343,7 +6330,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *str; IF_ASH_BASH_COMPAT(char *repl = NULL;) IF_ASH_BASH_COMPAT(int pos, len, orig_len;) - int saveherefd = herefd; int amount, resetloc; IF_ASH_BASH_COMPAT(int workloc;) int zero; @@ -6352,12 +6338,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype, //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)", // p, varname, strloc, subtype, startloc, varflags, quotes); - herefd = -1; argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0), var_str_list); STPUTC('\0', expdest); - herefd = saveherefd; argbackq = saveargbackq; startp = (char *)stackblock() + startloc; @@ -7391,7 +7375,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag) static void expandhere(union node *arg, int fd) { - herefd = fd; expandarg(arg, (struct arglist *)NULL, EXP_QUOTED); full_write(fd, stackblock(), expdest - (char *)stackblock()); } -- cgit v1.2.3-55-g6feb From 2a6d29ad5ce652c03ed8324a0d14f62594f2dd84 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 21:17:52 +0200 Subject: ash: [PARSER] Do not show prompts in expandstr Upstream patch: Date: Thu, 27 Dec 2007 13:57:07 +1100 [PARSER] Do not show prompts in expandstr Once I fixed the previous problem it became apparent that we never dealt with prompts with new-lines in them correctly. The problem is that we showed a secondary prompt for each of them. This patch disables prompt generation in expandstr. Signed-off-by: Herbert Xu function old new delta expandstr 102 127 +25 Signed-off-by: Denys Vlasenko --- shell/ash.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shell/ash.c b/shell/ash.c index 527abc7b1..dc0f60747 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12276,11 +12276,17 @@ static const char * expandstr(const char *ps) { union node n; + int saveprompt; /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value, * and token processing _can_ alter it (delete NULs etc). */ setinputstring((char *)ps); + + saveprompt = doprompt; + doprompt = 0; readtoken1(pgetc(), PSSYNTAX, nullstr, 0); + doprompt = saveprompt; + popfile(); n.narg.type = NARG; -- cgit v1.2.3-55-g6feb From eaf9436b08f68be8b96730307fe1da1faf374ee0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Oct 2016 21:46:03 +0200 Subject: ash: [REDIR] Move null redirect checks into caller Upstream commit: Date: Thu, 27 May 2010 14:21:17 +0800 [REDIR] Move null redirect checks into caller The null redirect checks were added as an optimisation to avoid unnecessary memory allocations. However, we could avoid this completely by simply making the caller avoid making a redirection unless it is not null. Signed-off-by: Herbert Xu function old new delta evaltree 784 809 +25 evalcommand 1251 1261 +10 hashvar 59 62 +3 dotcmd 321 319 -2 clearredir 37 30 -7 popredir 183 162 -21 redirect 1264 1233 -31 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/4 up/down: 63/-61) Total: -23 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index dc0f60747..dc26bc142 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1951,7 +1951,6 @@ struct redirtab; struct globals_var { struct shparam shellparam; /* $@ current positional parameters */ struct redirtab *redirlist; - int g_nullredirs; int preverrout_fd; /* save fd2 before print debug if xflag is set. */ struct var *vartab[VTABSIZE]; struct var varinit[ARRAY_SIZE(varinit_data)]; @@ -1960,7 +1959,6 @@ extern struct globals_var *const ash_ptr_to_globals_var; #define G_var (*ash_ptr_to_globals_var) #define shellparam (G_var.shellparam ) //#define redirlist (G_var.redirlist ) -#define g_nullredirs (G_var.g_nullredirs ) #define preverrout_fd (G_var.preverrout_fd) #define vartab (G_var.vartab ) #define varinit (G_var.varinit ) @@ -5216,7 +5214,6 @@ struct two_fd_t { }; struct redirtab { struct redirtab *next; - int nullredirs; int pair_count; struct two_fd_t two_fd[]; }; @@ -5295,7 +5292,6 @@ redirect(union node *redir, int flags) int newfd; int copied_fd2 = -1; - g_nullredirs++; if (!redir) { return; } @@ -5317,8 +5313,6 @@ redirect(union node *redir, int flags) sv->next = redirlist; sv->pair_count = sv_pos; redirlist = sv; - sv->nullredirs = g_nullredirs - 1; - g_nullredirs = 0; while (sv_pos > 0) { sv_pos--; sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY; @@ -5430,7 +5424,7 @@ popredir(int drop, int restore) struct redirtab *rp; int i; - if (--g_nullredirs >= 0 || redirlist == NULL) + if (redirlist == NULL) return; INT_OFF; rp = redirlist; @@ -5452,7 +5446,6 @@ popredir(int drop, int restore) } } redirlist = rp->next; - g_nullredirs = rp->nullredirs; free(rp); INT_ON; } @@ -5467,12 +5460,8 @@ popredir(int drop, int restore) static void clearredir(int drop) { - for (;;) { - g_nullredirs = 0; - if (!redirlist) - break; + while (redirlist) popredir(drop, /*restore:*/ 0); - } } static int @@ -8573,7 +8562,8 @@ evaltree(union node *n, int flags) if (!status) { status = evaltree(n->nredir.n, flags & EV_TESTED); } - popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); + if (n->nredir.redirect) + popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); goto setstatus; case NCMD: evalfn = evalcommand; @@ -9645,7 +9635,8 @@ evalcommand(union node *cmd, int flags) } /* switch */ out: - popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); + if (cmd->ncmd.redirect) + popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); if (lastarg) { /* dsl: I think this is intended to be used to support * '_' in 'vi' command mode during line editing... -- cgit v1.2.3-55-g6feb From 88e15703acdbfb182440cf35fdb8972fc4931dd2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Oct 2016 01:55:56 +0200 Subject: ash: [PARSER] Report substition errors at expansion time Upstreams commit: Date: Mon, 8 Oct 2007 21:32:25 +0800 [PARSER] Report substition errors at expansion time On Wed, Apr 11, 2007 at 01:24:21PM -0700, Micah Cowan wrote: > This operation fails on Ubuntu: > > $ /bin/sh -c 'if false; then d="${foo/bar}"; fi' > /bin/sh: Syntax error: Bad substitution > > When used with other POSIX shells it succeeds. While semantically the > variable reference ${foo/bar} is not valid, this is not a syntax error > according to POSIX, and since the variable assignment expression is > never invoked (because it's within an "if false") it should not be seen > as an error. > > I ran into this because after restarting my system I could no longer log > in. It turns out that the problem was (a) I had edited .gnomerc to > source my .bashrc file so that my environment would be set properly, and > (b) I had added some new code to my .bashrc WITHIN A CHECK FOR BASH! > that used bash's ${var/match/sub} feature. Even though this code was > within a "case $BASH_VERSION; in *[0-9]*) ... esac (so dash would never > execute it since that variable is not set), it still caused dash to > throw up. > > FYI, some relevant details from POSIX: > > Section 2.3, Token Recognition: > > 5. If the current character is an unquoted '$' or '`', the shell shall > identify the start of any candidates for parameter expansion ( Parameter > Expansion), command substitution ( Command Substitution), or arithmetic > expansion ( Arithmetic Expansion) from their introductory unquoted > character sequences: '$' or "${", "$(" or '`', and "$((", respectively. > The shell shall read sufficient input to determine the end of the unit > to be expanded (as explained in the cited sections). > > Section 2.6.2, Parameter Expansion: > > The format for parameter expansion is as follows: > > ${expression} > > where expression consists of all characters until the matching '}'. Any > '}' escaped by a backslash or within a quoted string, and characters in > embedded arithmetic expansions, command substitutions, and variable > expansions, shall not be examined in determining the matching '}'. > [...] > > The parameter name or symbol can be enclosed in braces, which are > optional except for positional parameters with more than one digit or > when parameter is followed by a character that could be interpreted as > part of the name. The matching closing brace shall be determined by > counting brace levels, skipping over enclosed quoted strings, and > command substitutions. > --- > In addition to bash I've checked Solaris /bin/sh and ksh and they don't > report an error. > > ----- > Micah Cowan: > > The applicable portion of POSIX is in XCU 2.10.1: > > "The WORD tokens shall have the word expansion rules applied to them > immediately before the associated command is executed, not at the time > the command is parsed." > > This seems fairly clear to me. This patch moves the error detection to expansion time. Test case: if false; then echo ${a!7} fi echo OK Old result: dash: Syntax error: Bad substitution New result: OK function old new delta evalvar 574 585 +11 readtoken1 2763 2750 -13 Signed-off-by: Denys Vlasenko --- shell/ash.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index dc26bc142..e30d7fe67 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6752,6 +6752,10 @@ evalvar(char *p, int flag, struct strlist *var_str_list) varflags = (unsigned char) *p++; subtype = varflags & VSTYPE; + + if (!subtype) + raise_error_syntax("bad substitution"); + quoted = flag & EXP_QUOTED; var = p; easy = (!quoted || (*var == '@' && shellparam.nparam)); @@ -11699,7 +11703,7 @@ parseredir: { parsesub: { unsigned char subtype; int typeloc; - int flags; + int flags = 0; c = pgetc_eatbnl(); if (c > 255 /* PEOA or PEOF */ @@ -11759,15 +11763,13 @@ parsesub: { USTPUTC(c, out); c = pgetc_eatbnl(); } else { - badsub: - raise_error_syntax("bad substitution"); + goto badsub; } if (c != '}' && subtype == VSLENGTH) { /* ${#VAR didn't end with } */ goto badsub; } - STPUTC('=', out); flags = 0; if (subtype == 0) { static const char types[] ALIGN1 = "}-+?="; @@ -11784,7 +11786,7 @@ parsesub: { if (!strchr(types, c)) { subtype = VSSUBSTR; pungetc(); - break; /* "goto do_pungetc" is bigger (!) */ + break; /* "goto badsub" is bigger (!) */ } #endif flags = VSNUL; @@ -11792,7 +11794,7 @@ parsesub: { default: { const char *p = strchr(types, c); if (p == NULL) - goto badsub; + break; subtype = p - types + VSNORMAL; break; } @@ -11802,7 +11804,7 @@ parsesub: { subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); c = pgetc_eatbnl(); if (c != cc) - goto do_pungetc; + goto badsub; subtype++; break; } @@ -11814,13 +11816,13 @@ parsesub: { subtype = VSREPLACE; c = pgetc_eatbnl(); if (c != '/') - goto do_pungetc; + goto badsub; subtype++; /* VSREPLACEALL */ break; #endif } } else { - do_pungetc: + badsub: pungetc(); } ((unsigned char *)stackblock())[typeloc] = subtype | flags; @@ -11830,6 +11832,7 @@ parsesub: { dqvarnest++; } } + STPUTC('=', out); } goto parsesub_return; } -- cgit v1.2.3-55-g6feb From a513bf3c3ce0756c991b21c0ca271e24fedcdb51 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Oct 2016 02:03:37 +0200 Subject: ash: [BUILTIN] Treat OPTIND=0 in the same way as OPTIND=1 Upstream commit: Date: Sat, 6 Oct 2007 18:59:31 +0800 [BUILTIN] Treat OPTIND=0 in the same way as OPTIND=1 Previously setting OPTIND to 0 would cause subsequent getopts calls to fail. This patch makes dash reset the getopts parameters the same way as OPTIND=1. Both behaviours are allowed by POSIX but other common shells do tolerate this case. function old new delta getoptsreset 24 30 +6 getoptscmd 632 614 -18 Signed-off-by: Denys Vlasenko --- shell/ash.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index e30d7fe67..647fc81b4 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2019,7 +2019,7 @@ extern struct globals_var *const ash_ptr_to_globals_var; static void FAST_FUNC getoptsreset(const char *value) { - shellparam.optind = number(value); + shellparam.optind = number(value) ?: 1; shellparam.optoff = -1; } #endif @@ -10571,8 +10571,6 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt sbuf[1] = '\0'; - if (*param_optind < 1) - return 1; optnext = optfirst + *param_optind - 1; if (*param_optind <= 1 || *optoff < 0 || (int)strlen(optnext[-1]) < *optoff) -- cgit v1.2.3-55-g6feb