From bugzilla at busybox.net Fri Jun 2 08:34:22 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Fri, 02 Jun 2023 08:34:22 +0000 Subject: [Bug 15616] New: adduser shows uid '200' in use when the gid is in use Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15616 Bug ID: 15616 Summary: adduser shows uid '200' in use when the gid is in use Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: minor Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: sven.kirmess at kzone.ch CC: busybox-cvs at busybox.net Target Milestone: --- adduser incorrectly reports the uid 200 to be already in use - the actual problem is that it tries to create a group with gid 200 and the gid is already in use. The error message should read "adduser: gid '200' in use" or maybe even "adduser: gid '200' in use by group nofiles" to make it clear what the problem is. $ docker run --rm -it alpine sh / # adduser -u 200 test adduser: uid '200' in use / # grep 200 /etc/passwd /etc/group /etc/group:nofiles:x:200: / # adduser -u 200 -G nofiles -D test https://gitlab.alpinelinux.org/alpine/aports/-/issues/14954 -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Fri Jun 2 22:42:10 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 3 Jun 2023 00:42:10 +0200 Subject: [git commit] awk: fix backslash handling in sub() builtins Message-ID: <20230602224235.23BC686C07@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=5f84c5633663f6ee8c9cc3a4608b86d4b56b39d6 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta awk_sub 559 544 -15 Signed-off-by: Denys Vlasenko --- editors/awk.c | 41 +++++++++++++++++++---------------------- testsuite/awk.tests | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/editors/awk.c b/editors/awk.c index 0f062dcdb..f77573806 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -2492,7 +2492,7 @@ static char *awk_printf(node *n, size_t *len) * store result into (dest), return number of substitutions. * If nm = 0, replace all matches. * If src or dst is NULL, use $0. - * If subexp != 0, enable subexpression matching (\1-\9). + * If subexp != 0, enable subexpression matching (\0-\9). */ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int subexp) { @@ -2520,35 +2520,32 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int residx += eo; if (++match_no >= nm) { const char *s; - int nbs; + int bslash; /* replace */ residx -= (eo - so); - nbs = 0; + bslash = 0; for (s = repl; *s; s++) { - char c = resbuf[residx++] = *s; - if (c == '\\') { - nbs++; - continue; + char c = *s; + if (c == '\\' && s[1]) { + bslash ^= 1; + if (bslash) + continue; } - if (c == '&' || (subexp && c >= '0' && c <= '9')) { - int j; - residx -= ((nbs + 3) >> 1); - j = 0; + if ((!bslash && c == '&') + || (subexp && bslash && c >= '0' && c <= '9') + ) { + int n, j = 0; if (c != '&') { j = c - '0'; - nbs++; } - if (nbs % 2) { - resbuf[residx++] = c; - } else { - int n = pmatch[j].rm_eo - pmatch[j].rm_so; - resbuf = qrealloc(resbuf, residx + replen + n, &resbufsize); - memcpy(resbuf + residx, sp + pmatch[j].rm_so, n); - residx += n; - } - } - nbs = 0; + n = pmatch[j].rm_eo - pmatch[j].rm_so; + resbuf = qrealloc(resbuf, residx + replen + n, &resbufsize); + memcpy(resbuf + residx, sp + pmatch[j].rm_so, n); + residx += n; + } else + resbuf[residx++] = c; + bslash = 0; } } diff --git a/testsuite/awk.tests b/testsuite/awk.tests index cdab93d21..c61d32947 100755 --- a/testsuite/awk.tests +++ b/testsuite/awk.tests @@ -552,4 +552,51 @@ testing "awk = has higher precedence than == (despite what gawk manpage claims)" '0\n1\n2\n1\n3\n' \ '' '' +sq="'" +testing 'awk gensub backslashes \' \ + 'awk '$sq'BEGIN { s="\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\ +\\|\\ +' \ + '' '' +testing 'awk gensub backslashes \\' \ + 'awk '$sq'BEGIN { s="\\\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\\\ +\\|\\ +' \ + '' '' +# gawk 5.1.1 handles trailing unpaired \ inconsistently. +# If replace string is single \, it is used verbatim, +# but if it is \\\ (three slashes), gawk uses "\" (!!!), not "\\" as you would expect. +testing 'awk gensub backslashes \\\' \ + 'awk '$sq'BEGIN { s="\\\\\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\\\\\ +\\\\|\\\\ +' \ + '' '' +testing 'awk gensub backslashes \\\\' \ + 'awk '$sq'BEGIN { s="\\\\\\\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\\\\\\\ +\\\\|\\\\ +' \ + '' '' +testing 'awk gensub backslashes \&' \ + 'awk '$sq'BEGIN { s="\\&"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\& +&|& +' \ + '' '' +testing 'awk gensub backslashes \0' \ + 'awk '$sq'BEGIN { s="\\0"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\0 +a|a +' \ + '' '' +testing 'awk gensub backslashes \\0' \ + 'awk '$sq'BEGIN { s="\\\\0"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ + 's=\\\\0 +\\0|\\0 +' \ + '' '' + exit $FAILCOUNT From bugzilla at busybox.net Mon Jun 5 12:52:15 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Mon, 05 Jun 2023 12:52:15 +0000 Subject: [Bug 15619] New: Odd beavor of loops with async sleep in 1.136 Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15619 Bug ID: 15619 Summary: Odd beavor of loops with async sleep in 1.136 Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: major Priority: P5 Component: Standard Compliance Assignee: unassigned at busybox.net Reporter: busybox at f2o.io CC: busybox-cvs at busybox.net Target Milestone: --- If the first command of a for or while loop is forked the following sleep seems to be forked or maybe even skipped too. Example broken: for i in 1 2 3 4 5 6 7 8 9; do echo test & sleep 5; done; echo test2 The former command does just take a few milliseconds. Compared to for i in 1 2 3 4 5 6 7 8 9; do echo test & /bin/sleep 5; done; echo test2 which takes quite some time. This issue was not existing in 1.35. Tested in an Alpine Linux Docker container. Thanks for your time! -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Tue Jun 6 10:48:11 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 6 Jun 2023 12:48:11 +0200 Subject: [git commit] awk: code shrink Message-ID: <20230606104915.A8F4A86CB9@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=f4789164e0716a8b1f98cf4149a3eb2dad485b8b branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta awk_sub 544 548 +4 exec_builtin 1136 1130 -6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 4/-6) Total: -2 bytes Signed-off-by: Denys Vlasenko --- editors/awk.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/editors/awk.c b/editors/awk.c index f77573806..b3871ffc5 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -2494,7 +2494,7 @@ static char *awk_printf(node *n, size_t *len) * If src or dst is NULL, use $0. * If subexp != 0, enable subexpression matching (\0-\9). */ -static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int subexp) +static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest /*,int subexp*/) { char *resbuf; const char *sp; @@ -2502,6 +2502,8 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int int regexec_flags; regmatch_t pmatch[10]; regex_t sreg, *regex; + /* True only if called to implement gensub(): */ + int subexp = (src != dest); resbuf = NULL; residx = 0; @@ -2549,7 +2551,6 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int } } - regexec_flags = REG_NOTBOL; sp += eo; if (match_no == nm) break; @@ -2570,6 +2571,7 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int sp++; residx++; } + regexec_flags = REG_NOTBOL; } resbuf = qrealloc(resbuf, residx + strlen(sp), &resbufsize); @@ -2798,16 +2800,16 @@ static NOINLINE var *exec_builtin(node *op, var *res) res = do_match(an[1], as[0]); break; - case B_ge: - awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE); + case B_ge: /* gensub(regex, repl, matchnum, string) */ + awk_sub(an[0], as[1], /*matchnum:*/getvar_i(av[2]), /*src:*/av[3], /*dst:*/res/*, TRUE*/); break; - case B_gs: - setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE)); + case B_gs: /* gsub(regex, repl, string) */ + setvar_i(res, awk_sub(an[0], as[1], /*matchnum:all*/0, /*src:*/av[2], /*dst:*/av[2]/*, FALSE*/)); break; - case B_su: - setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE)); + case B_su: /* sub(regex, repl, string) */ + setvar_i(res, awk_sub(an[0], as[1], /*matchnum:first*/1, /*src:*/av[2], /*dst:*/av[2]/*, FALSE*/)); break; } From bugzilla at busybox.net Tue Jun 6 14:21:35 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Tue, 06 Jun 2023 14:21:35 +0000 Subject: [Bug 15622] New: mv timestamp awkward behaviour Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15622 Bug ID: 15622 Summary: mv timestamp awkward behaviour Product: Busybox Version: 1.35.x Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: friedtj at free.fr CC: busybox-cvs at busybox.net Target Milestone: --- Created attachment 9604 --> https://bugs.busybox.net/attachment.cgi?id=9604&action=edit busybox configuration file I need to remember a file last access with sub-second accuracy. On a Raspberry Pi4 running Buildroot 2022.08.1 (raspberrypi4_64_defconfig) using Busybox 1.35.0: 1. create a file on the /tmpfs of the Raspberry Pi 4 TWSTFT:13:44# touch toto TWSTFT:13:44# stat toto File: toto Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 13h/19d Inode: 10525 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2023-06-06 13:44:44.804197892 +0000 Modify: 2023-06-06 13:44:44.804197892 +0000 Change: 2023-06-06 13:44:44.804197892 +0000 All times are given with ns resolution. When moving the file using Busybox's mv from /tmp (tmpfs) to an external hard disk formatted in EXT4: TWSTFT:13:44# cd /root/data/ TWSTFT:13:44# mv /tmp/toto . TWSTFT:13:45# stat toto File: toto Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 801h/2049d Inode: 303 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2023-06-06 13:44:44.000000000 +0000 Modify: 2023-06-06 13:44:44.000000000 +0000 Change: 2023-06-06 13:45:02.224085953 +0000 The Access and Modify times are now truncated to one-second resolution. If on the same HDD I create a file TWSTFT:13:45# touch hdd TWSTFT:13:45# stat hdd File: hdd Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 801h/2049d Inode: 304 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2023-06-06 13:45:11.468026512 +0000 Modify: 2023-06-06 13:45:11.468026512 +0000 Change: 2023-06-06 13:45:11.468026512 +0000 the statistics with full resolution are kept. Is this a bug in mv or a feature? Thanks. -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Wed Jun 7 08:54:34 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 7 Jun 2023 10:54:34 +0200 Subject: [git commit] awk: fix SEGV on read error in -f PROGFILE Message-ID: <20230607085520.8BC7A86D31@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=113685fbcd4c3432ec9b640583d50ba8da2102e8 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta awk_main 829 843 +14 Signed-off-by: Denys Vlasenko --- editors/awk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/awk.c b/editors/awk.c index b3871ffc5..df9b7fdc9 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -3609,8 +3609,6 @@ static var *evaluate(node *op, var *res) #undef sreg } -/* -------- main & co. -------- */ - static int awk_exit(void) { unsigned i; @@ -3717,6 +3715,8 @@ int awk_main(int argc UNUSED_PARAM, char **argv) g_progname = llist_pop(&list_f); fd = xopen_stdin(g_progname); s = xmalloc_read(fd, NULL); /* it's NUL-terminated */ + if (!s) + bb_perror_msg_and_die("read error from '%s'", g_progname); close(fd); parse_program(s); free(s); From vda.linux at googlemail.com Thu Jun 8 08:42:39 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 8 Jun 2023 10:42:39 +0200 Subject: [git commit] awk: fix subst code to handle "start of word" pattern correctly (needs REG_STARTEND) Message-ID: <20230608084404.921A386D8E@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=2ca39ffd447ca874fcea933194829717d5573247 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta awk_sub 637 714 +77 Signed-off-by: Denys Vlasenko --- editors/awk.c | 49 ++++++++++++++++++++++++++++++++++++------------- testsuite/awk.tests | 28 +++++++++++++++------------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/editors/awk.c b/editors/awk.c index df9b7fdc9..171f0a7ea 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -2504,17 +2504,46 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest /*,in regex_t sreg, *regex; /* True only if called to implement gensub(): */ int subexp = (src != dest); - +#if defined(REG_STARTEND) + const char *src_string; + size_t src_strlen; + regexec_flags = REG_STARTEND; +#else + regexec_flags = 0; +#endif resbuf = NULL; residx = 0; match_no = 0; - regexec_flags = 0; regex = as_regex(rn, &sreg); sp = getvar_s(src ? src : intvar[F0]); +#if defined(REG_STARTEND) + src_string = sp; + src_strlen = strlen(src_string); +#endif replen = strlen(repl); - while (regexec(regex, sp, 10, pmatch, regexec_flags) == 0) { - int so = pmatch[0].rm_so; - int eo = pmatch[0].rm_eo; + for (;;) { + int so, eo; + +#if defined(REG_STARTEND) +// REG_STARTEND: "This flag is a BSD extension, not present in POSIX" + size_t start_ofs = sp - src_string; + pmatch[0].rm_so = start_ofs; + pmatch[0].rm_eo = src_strlen; + if (regexec(regex, src_string, 10, pmatch, regexec_flags) != 0) + break; + eo = pmatch[0].rm_eo - start_ofs; + so = pmatch[0].rm_so - start_ofs; +#else +// BUG: +// gsub(/\" (!!!), not "\\" as you would expect. @@ -572,31 +570,35 @@ testing 'awk gensub backslashes \\\' \ 'awk '$sq'BEGIN { s="\\\\\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ 's=\\\\\\ \\\\|\\\\ -' \ - '' '' +' '' '' testing 'awk gensub backslashes \\\\' \ 'awk '$sq'BEGIN { s="\\\\\\\\"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ 's=\\\\\\\\ \\\\|\\\\ -' \ - '' '' +' '' '' testing 'awk gensub backslashes \&' \ 'awk '$sq'BEGIN { s="\\&"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ 's=\\& &|& -' \ - '' '' +' '' '' testing 'awk gensub backslashes \0' \ 'awk '$sq'BEGIN { s="\\0"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ 's=\\0 a|a -' \ - '' '' +' '' '' testing 'awk gensub backslashes \\0' \ 'awk '$sq'BEGIN { s="\\\\0"; print "s=" s; print gensub("a", s, "g", "a|a") }'$sq \ 's=\\\\0 \\0|\\0 -' \ +' '' '' + +# The "b" in "abc" should not match References: Message-ID: https://bugs.busybox.net/show_bug.cgi?id=1333 Denys Vlasenko changed: What |Removed |Added ---------------------------------------------------------------------------- Resolution|--- |FIXED Status|NEW |RESOLVED --- Comment #2 from Denys Vlasenko --- commit 2ca39ffd447ca874fcea933194829717d5573247 Date: Thu Jun 8 10:42:39 2023 +0200 awk: fix subst code to handle "start of word" pattern correctly (needs REG_STARTEND) -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Sat Jun 10 09:57:57 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Sat, 10 Jun 2023 09:57:57 +0000 Subject: [Bug 15622] mv timestamp awkward behaviour In-Reply-To: References: Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15622 --- Comment #1 from JM Friedt --- This appears to be due to times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime; times[1].tv_usec = times[0].tv_usec = 0; in libbb/copy_file.c in int FAST_FUNC copy_file(const char *source, const char *dest, int flags) which sets the fractional part of the time to 0 irrelevant of the selected options. Is this behaviour expected when coreutils/stat.c states static const char *human_time(struct timespec *ts) { char fmt[sizeof("%Y-%m-%d %H:%M:%S.123456789 %z") + /*paranoia*/ 8]; /* coreutils 6.3 compat */ #define buf bb_common_bufsiz1 setup_common_bufsiz(); sprintf(stpcpy(fmt, "%Y-%m-%d %H:%M:%S"), ".%09u %%z", (unsigned)ts->tv_nsec); strftime(buf, COMMON_BUFSIZE, fmt, localtime(&ts->tv_sec)); return buf; #undef buf } able to cope with sub-second resolution? -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Mon Jun 12 08:19:29 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 12 Jun 2023 10:19:29 +0200 Subject: [git commit] libbb: pass "" rather than NULL as format string in _nomsg functions Message-ID: <20230612082018.20F5982767@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=4bd70463c7e7511aea9de58876f0beacbcec1779 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta bb_perror_nomsg_and_die 9 10 +1 bb_perror_nomsg 9 10 +1 bb_verror_msg 480 469 -11 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 2/-11) Total: -9 bytes Signed-off-by: Denys Vlasenko --- libbb/perror_nomsg.c | 12 ++---------- libbb/perror_nomsg_and_die.c | 12 ++---------- libbb/verror_msg.c | 6 ------ 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/libbb/perror_nomsg.c b/libbb/perror_nomsg.c index a2a11cc8e..670ccbc0c 100644 --- a/libbb/perror_nomsg.c +++ b/libbb/perror_nomsg.c @@ -6,17 +6,9 @@ * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ +#include "libbb.h" -/* gcc warns about a null format string, therefore we provide - * modified definition without "attribute (format)" - * instead of including libbb.h */ -//#include "libbb.h" -#include "platform.h" -extern void bb_perror_msg(const char *s, ...) FAST_FUNC; - -/* suppress gcc "no previous prototype" warning */ -void FAST_FUNC bb_perror_nomsg(void); void FAST_FUNC bb_perror_nomsg(void) { - bb_perror_msg(0); + bb_simple_perror_msg(""); } diff --git a/libbb/perror_nomsg_and_die.c b/libbb/perror_nomsg_and_die.c index 543ff5178..4151e0246 100644 --- a/libbb/perror_nomsg_and_die.c +++ b/libbb/perror_nomsg_and_die.c @@ -6,17 +6,9 @@ * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ +#include "libbb.h" -/* gcc warns about a null format string, therefore we provide - * modified definition without "attribute (format)" - * instead of including libbb.h */ -//#include "libbb.h" -#include "platform.h" -extern void bb_perror_msg_and_die(const char *s, ...) FAST_FUNC; - -/* suppress gcc "no previous prototype" warning */ -void FAST_FUNC bb_perror_nomsg_and_die(void); void FAST_FUNC bb_perror_nomsg_and_die(void) { - bb_perror_msg_and_die(0); + bb_simple_perror_msg_and_die(""); } diff --git a/libbb/verror_msg.c b/libbb/verror_msg.c index 71512aee2..74b608f4c 100644 --- a/libbb/verror_msg.c +++ b/libbb/verror_msg.c @@ -26,9 +26,6 @@ void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr) if (!logmode) return; - if (!s) /* nomsg[_and_die] uses NULL fmt */ - s = ""; /* some libc don't like printf(NULL) */ - applet_len = strlen(applet_name) + 2; /* "applet: " */ strerr_len = strerr ? strlen(strerr) : 0; msgeol_len = strlen(msg_eol); @@ -116,9 +113,6 @@ void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr) if (!logmode) return; - if (!s) /* nomsg[_and_die] uses NULL fmt */ - s = ""; /* some libc don't like printf(NULL) */ - /* Prevent "derefing type-punned ptr will break aliasing rules" */ used = vasprintf((char**)(void*)msgptr, s, p); if (used < 0) From vda.linux at googlemail.com Mon Jun 12 11:22:47 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 12 Jun 2023 13:22:47 +0200 Subject: [git commit] udhcpd: optional BOOTP support Message-ID: <20230612112356.8886A827D6@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=8f0e4c42c630126daafd834175b86d2b28f5b798 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master from Adam Goldman This patch makes udhcpd respond correctly to queries from BOOTP clients. It contains the following changes: The end field, or DHCP_END option, is required in DHCP requests but optional in BOOTP requests. However, we still send an end field in all replies, because some BOOTP clients expect one in replies even if they didn't send one in the request. Requests without a DHCP_MESSAGE_TYPE are recognized as BOOTP requests and handled appropriately, instead of being discarded. We still require an RFC 1048 options field, but we allow it to be empty. Since a BOOTP client will keep using the assigned IP forever, we only send a BOOTP reply if a static lease exists for that client. BOOTP replies shouldn't contain DHCP_* options, so we omit them if there was no DHCP_MESSAGE_TYPE in the request. Options other than DHCP_* options are still sent. The options field of a BOOTP reply must be exactly 64 bytes. If we construct a reply with more than 64 bytes of options, we give up and log an error instead of sending it. udhcp_send_raw_packet already pads the options field to 64 bytes if it is too short. This implementation has been tested against an HP PA-RISC client. function old new delta .rodata 105247 105321 +74 udhcpd_main 1520 1591 +71 send_offer 419 470 +51 init_packet 81 97 +16 udhcp_init_header 75 88 +13 udhcp_scan_options 192 203 +11 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 6/0 up/down: 236/0) Total: 236 bytes Signed-off-by: Denys Vlasenko --- networking/udhcp/Config.src | 7 +++++++ networking/udhcp/common.c | 10 ++++++++- networking/udhcp/common.h | 14 ++++++++++--- networking/udhcp/dhcpc.c | 2 +- networking/udhcp/dhcpd.c | 49 ++++++++++++++++++++++++++++++++++++++------- networking/udhcp/packet.c | 9 ++++++--- 6 files changed, 76 insertions(+), 15 deletions(-) diff --git a/networking/udhcp/Config.src b/networking/udhcp/Config.src index 7ba7f48fc..d9c501c18 100644 --- a/networking/udhcp/Config.src +++ b/networking/udhcp/Config.src @@ -10,6 +10,13 @@ config UDHCPD udhcpd is a DHCP server geared primarily toward embedded systems, while striving to be fully functional and RFC compliant. +config FEATURE_UDHCPD_BOOTP + bool "Answer to BOOTP requests as well" + default y + depends on UDHCPD + help + Support old BOOTP protocol too. + config FEATURE_UDHCPD_BASE_IP_ON_MAC bool "Select IP address based on client MAC" default n diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c index ae818db05..ad580f38d 100644 --- a/networking/udhcp/common.c +++ b/networking/udhcp/common.c @@ -252,6 +252,14 @@ uint8_t* FAST_FUNC udhcp_scan_options(struct dhcp_packet *packet, struct dhcp_sc /* option bytes: [code][len][data1][data2]..[dataLEN] */ while (1) { if (scan_state->rem <= 0) { + if (ENABLE_FEATURE_UDHCPD_BOOTP && scan_state->rem == 0) { + /* DHCP requires END option to be present. + * We are here if packet fails this condition + * (options[] are zero-padded to the end). + * Assume BOOTP packet without further checks. + */ + break; /* return NULL */ + } complain: bb_simple_error_msg("bad packet, malformed option field"); return NULL; @@ -278,7 +286,7 @@ uint8_t* FAST_FUNC udhcp_scan_options(struct dhcp_packet *packet, struct dhcp_sc scan_state->rem = sizeof(packet->sname); continue; } - break; + break; /* return NULL */ } if (scan_state->rem <= OPT_LEN) /* [len] byte exists? */ diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h index 49a0b593d..3ef371a7c 100644 --- a/networking/udhcp/common.h +++ b/networking/udhcp/common.h @@ -20,8 +20,11 @@ extern const uint8_t MAC_BCAST_ADDR[6] ALIGN2; /* six all-ones */ /*** DHCP packet ***/ +#define RFC1048_MAGIC 0x63825363 +/* RFC 1048 still uses BOOTP's small buffer (4 byte cookie + 60 the rest) */ +#define RFC1048_OPTIONS_BUFSIZE 60 + /* DHCP protocol. See RFC 2131 */ -#define DHCP_MAGIC 0x63825363 #define DHCP_OPTIONS_BUFSIZE 308 #define BOOTREQUEST 1 #define BOOTREPLY 2 @@ -57,8 +60,10 @@ struct dhcp_packet { * such as 'unix' or 'gateway'; this means 'boot the named program * configured for my machine'" */ - /* BOOTP fields end here, BOOTP says optional uint8_t vend[64] follows */ - uint32_t cookie; /* DHCP magic bytes: 99,130,83,99 decimal */ + /* BOOTP fields end here, BOOTP says optional uint8_t vend[64] follows. */ + /* RFC 1048 defined this cookie value and options 0-12 and 255. */ + /* DHCP extended it and required option 255 (END) to be always present. */ + uint32_t cookie; /* RFC 1048 magic bytes: 99,130,83,99 decimal */ uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS]; }; #define DHCP_PKT_SNAME_LEN 64 @@ -200,6 +205,9 @@ struct dhcp_scan_state { #define SNAME_FIELD 2 /* DHCP_MESSAGE_TYPE values */ +#if ENABLE_FEATURE_UDHCPD_BOOTP +#define MSGTYPE_BOOTP 0 /* there was no TYPE option in client's packet, assuming BOOTP */ +#endif #define DHCPDISCOVER 1 /* client -> server */ #define DHCPOFFER 2 /* client <- server */ #define DHCPREQUEST 3 /* client -> server */ diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c index c757fb37c..200a2fb8a 100644 --- a/networking/udhcp/dhcpc.c +++ b/networking/udhcp/dhcpc.c @@ -971,7 +971,7 @@ static NOINLINE int d4_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd) } skip_udp_sum_check: - if (packet.data.cookie != htonl(DHCP_MAGIC)) { + if (packet.data.cookie != htonl(RFC1048_MAGIC)) { log1s("packet with bad magic, ignoring"); return -2; } diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c index 66750e2e6..2904119e5 100644 --- a/networking/udhcp/dhcpd.c +++ b/networking/udhcp/dhcpd.c @@ -649,7 +649,8 @@ static void init_packet(struct dhcp_packet *packet, struct dhcp_packet *oldpacke packet->flags = oldpacket->flags; packet->gateway_nip = oldpacket->gateway_nip; packet->ciaddr = oldpacket->ciaddr; - udhcp_add_simple_option(packet, DHCP_SERVER_ID, server_data.server_nip); + IF_FEATURE_UDHCPD_BOOTP(if (type != MSGTYPE_BOOTP)) + udhcp_add_simple_option(packet, DHCP_SERVER_ID, server_data.server_nip); } /* Fill options field, siaddr_nip, and sname and boot_file fields. @@ -725,7 +726,12 @@ static uint32_t select_lease_time(struct dhcp_packet *packet) /* We got a DHCP DISCOVER. Send an OFFER. */ /* NOINLINE: limit stack usage in caller */ -static NOINLINE void send_offer(struct dhcp_packet *oldpacket, +#if !ENABLE_FEATURE_UDHCPD_BOOTP +#define send_offer(is_dhcp_client, ...) \ + send_offer(__VA_ARGS__) +#endif +static NOINLINE void send_offer(void *is_dhcp_client, + struct dhcp_packet *oldpacket, uint32_t static_lease_nip, struct dyn_lease *lease, uint32_t requested_nip, @@ -734,7 +740,12 @@ static NOINLINE void send_offer(struct dhcp_packet *oldpacket, struct dhcp_packet packet; uint32_t lease_time_sec; +#if ENABLE_FEATURE_UDHCPD_BOOTP + init_packet(&packet, oldpacket, is_dhcp_client ? DHCPOFFER : MSGTYPE_BOOTP); +#else + enum { is_dhcp_client = 1 }; init_packet(&packet, oldpacket, DHCPOFFER); +#endif /* If it is a static lease, use its IP */ packet.yiaddr = static_lease_nip; @@ -784,9 +795,16 @@ static NOINLINE void send_offer(struct dhcp_packet *oldpacket, } } - lease_time_sec = select_lease_time(oldpacket); - udhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec)); + if (is_dhcp_client) { + lease_time_sec = select_lease_time(oldpacket); + udhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec)); + } +/* TODO: pass "is_dhcp_client" to add_server_options(), avoid adding confusing options to BOOTP clients? */ add_server_options(&packet); + if (!is_dhcp_client && udhcp_end_option(packet.options) >= RFC1048_OPTIONS_BUFSIZE) { + bb_simple_error_msg("BOOTP reply too large, not sending"); + return; + } /* send_packet emits error message itself if it detects failure */ send_packet_verbose(&packet, "sending OFFER to %s"); @@ -1050,8 +1068,12 @@ int udhcpd_main(int argc UNUSED_PARAM, char **argv) continue; } msg_type = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE); - if (!msg_type || msg_type[0] < DHCP_MINTYPE || msg_type[0] > DHCP_MAXTYPE) { - bb_info_msg("no or bad message type option%s", ", ignoring packet"); + if ( + IF_FEATURE_UDHCPD_BOOTP( msg_type && ) + IF_NOT_FEATURE_UDHCPD_BOOTP( !msg_type || ) + (msg_type[0] < DHCP_MINTYPE || msg_type[0] > DHCP_MAXTYPE) + ) { + bb_info_msg("bad message type option%s", ", ignoring packet"); continue; } @@ -1086,12 +1108,25 @@ int udhcpd_main(int argc UNUSED_PARAM, char **argv) move_from_unaligned32(requested_nip, requested_ip_opt); } +#if ENABLE_FEATURE_UDHCPD_BOOTP + /* Handle old BOOTP clients */ + if (!msg_type) { + log1("received %s", "BOOTP BOOTREQUEST"); + if (!static_lease_nip) { + bb_info_msg("no static lease for BOOTP client%s", ", ignoring packet"); + continue; + } + send_offer(msg_type, &packet, static_lease_nip, lease, requested_nip, arpping_ms); + continue; + } +#endif + switch (msg_type[0]) { case DHCPDISCOVER: log1("received %s", "DISCOVER"); - send_offer(&packet, static_lease_nip, lease, requested_nip, arpping_ms); + send_offer(msg_type, &packet, static_lease_nip, lease, requested_nip, arpping_ms); break; case DHCPREQUEST: diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c index 529978189..f9dc11d01 100644 --- a/networking/udhcp/packet.c +++ b/networking/udhcp/packet.c @@ -18,6 +18,8 @@ void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type) memset(packet, 0, sizeof(*packet)); packet->op = BOOTREQUEST; /* if client to a server */ switch (type) { + IF_FEATURE_UDHCPD_BOOTP(case MSGTYPE_BOOTP:) + /* reply to a BOOTP (not DHCP) client */ case DHCPOFFER: case DHCPACK: case DHCPNAK: @@ -25,10 +27,11 @@ void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type) } packet->htype = 1; /* ethernet */ packet->hlen = 6; - packet->cookie = htonl(DHCP_MAGIC); + packet->cookie = htonl(RFC1048_MAGIC); if (DHCP_END != 0) packet->options[0] = DHCP_END; - udhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type); + IF_FEATURE_UDHCPD_BOOTP(if (type != MSGTYPE_BOOTP)) + udhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type); } #endif @@ -90,7 +93,7 @@ int FAST_FUNC udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd) } if (bytes < offsetof(struct dhcp_packet, options) - || packet->cookie != htonl(DHCP_MAGIC) + || packet->cookie != htonl(RFC1048_MAGIC) ) { bb_simple_info_msg("packet with bad magic, ignoring"); return -2; From vda.linux at googlemail.com Mon Jun 12 14:39:32 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 12 Jun 2023 16:39:32 +0200 Subject: [git commit] hush: fix expansion of space in "a=${a:+$a }c" construct Message-ID: <20230612144105.5C8FA82800@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=bab8828b0dad1d51d6b34e38249f0641ca64b1e9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta encode_then_append_var_plusminus 554 552 -2 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/space_in_varexp1.right | 2 ++ shell/ash_test/ash-quoting/space_in_varexp1.tests | 6 ++++++ shell/hush.c | 2 +- shell/hush_test/hush-quoting/space_in_varexp1.right | 2 ++ shell/hush_test/hush-quoting/space_in_varexp1.tests | 6 ++++++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/shell/ash_test/ash-quoting/space_in_varexp1.right b/shell/ash_test/ash-quoting/space_in_varexp1.right new file mode 100644 index 000000000..a617a91c3 --- /dev/null +++ b/shell/ash_test/ash-quoting/space_in_varexp1.right @@ -0,0 +1,2 @@ +1:'b c' +2:'b c' diff --git a/shell/ash_test/ash-quoting/space_in_varexp1.tests b/shell/ash_test/ash-quoting/space_in_varexp1.tests new file mode 100755 index 000000000..1589587d1 --- /dev/null +++ b/shell/ash_test/ash-quoting/space_in_varexp1.tests @@ -0,0 +1,6 @@ +a=b +a=${a:+$a }c +echo "1:'$a'" +a=b +a="${a:+$a }c" +echo "2:'$a'" diff --git a/shell/hush.c b/shell/hush.c index cdaa67a3b..dbc4aecab 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6398,7 +6398,7 @@ static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n, if (!dest.o_expflags) { if (ch == EOF) break; - if (!dquoted && strchr(G.ifs, ch)) { + if (!dquoted && !(output->o_expflags & EXP_FLAG_SINGLEWORD) && strchr(G.ifs, ch)) { /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word. * do not assume we are at the start of the word (PREFIX above). */ diff --git a/shell/hush_test/hush-quoting/space_in_varexp1.right b/shell/hush_test/hush-quoting/space_in_varexp1.right new file mode 100644 index 000000000..a617a91c3 --- /dev/null +++ b/shell/hush_test/hush-quoting/space_in_varexp1.right @@ -0,0 +1,2 @@ +1:'b c' +2:'b c' diff --git a/shell/hush_test/hush-quoting/space_in_varexp1.tests b/shell/hush_test/hush-quoting/space_in_varexp1.tests new file mode 100755 index 000000000..1589587d1 --- /dev/null +++ b/shell/hush_test/hush-quoting/space_in_varexp1.tests @@ -0,0 +1,6 @@ +a=b +a=${a:+$a }c +echo "1:'$a'" +a=b +a="${a:+$a }c" +echo "2:'$a'" From vda.linux at googlemail.com Mon Jun 12 15:48:47 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 12 Jun 2023 17:48:47 +0200 Subject: [git commit] shell: avoid segfault on ${0::0/0~09J}. Closes 15216 Message-ID: <20230612155045.6252F82804@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=d417193cf37ca1005830d7e16f5fa7e1d8a44209 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1011 1053 +42 Signed-off-by: Denys Vlasenko --- shell/math.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/shell/math.c b/shell/math.c index 76d22c9bd..727c29467 100644 --- a/shell/math.c +++ b/shell/math.c @@ -577,6 +577,28 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) # endif #endif +//TODO: much better estimation than expr_len/2? Such as: +//static unsigned estimate_nums_and_names(const char *expr) +//{ +// unsigned count = 0; +// while (*(expr = skip_whitespace(expr)) != '\0') { +// const char *p; +// if (isdigit(*expr)) { +// while (isdigit(*++expr)) +// continue; +// count++; +// continue; +// } +// p = endofname(expr); +// if (p != expr) { +// expr = p; +// count++; +// continue; +// } +// } +// return count; +//} + static arith_t evaluate_string(arith_state_t *math_state, const char *expr) { @@ -584,10 +606,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) const char *errmsg; const char *start_expr = expr = skip_whitespace(expr); unsigned expr_len = strlen(expr) + 2; - /* Stack of integers */ - /* The proof that there can be no more than strlen(startbuf)/2+1 - * integers in any given correct or incorrect expression - * is left as an exercise to the reader. */ + /* Stack of integers/names */ + /* There can be no more than strlen(startbuf)/2+1 + * integers/names in any given correct or incorrect expression. + * (modulo "09v09v09v09v09v" case, + * but we have code to detect that early) + */ var_or_num_t *const numstack = alloca((expr_len / 2) * sizeof(numstack[0])); var_or_num_t *numstackptr = numstack; /* Stack of operator tokens */ @@ -652,6 +676,13 @@ evaluate_string(arith_state_t *math_state, const char *expr) numstackptr->var = NULL; errno = 0; numstackptr->val = strto_arith_t(expr, (char**) &expr); + /* A number can't be followed by another number, or a variable name. + * We'd catch this later anyway, but this would require numstack[] + * to be twice as deep to handle strings where _every_ char is + * a new number or name. Example: 09v09v09v09v09v09v09v09v09v + */ + if (isalnum(*expr) || *expr == '_') + goto err; //bb_error_msg("val:%lld", numstackptr->val); if (errno) numstackptr->val = 0; /* bash compat */ From vda.linux at googlemail.com Tue Jun 13 11:55:13 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 13 Jun 2023 13:55:13 +0200 Subject: [git commit] shell/math: reduce stack usage Message-ID: <20230613132259.53CAC82A14@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=a4f30f3c70550ee6ceea6768864bfb2e3a99a887 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1123 1134 +11 arith_lookup_val 140 145 +5 evaluate_string 1053 1047 -6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 16/-6) Total: 10 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 66 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/shell/math.c b/shell/math.c index 727c29467..af8e691fe 100644 --- a/shell/math.c +++ b/shell/math.c @@ -236,14 +236,19 @@ typedef struct { * then '?' selects one of them based on its left side. */ arith_t second_val; - char second_val_present; - /* If NULL then it's just a number, else it's a named variable */ - char *var; +#define SECOND_VAL_VALID ((char*)(intptr_t)-1) + /* If NULL then it's just a number, if SECOND_VAL_VALID, + * it's a result of "expr : expr", else it's a named variable. + * (We use SECOND_VAL_VALID instead of a bit flag to keep + * var_or_num_t smaller, we allocate a lot of them on stack). + */ + char *var_name; } var_or_num_t; + typedef struct remembered_name { struct remembered_name *next; - const char *var; + const char *var_name; } remembered_name; @@ -253,17 +258,17 @@ evaluate_string(arith_state_t *math_state, const char *expr); static const char* arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) { - if (t->var) { - const char *p = math_state->lookupvar(t->var); + if (t->var_name && t->var_name != SECOND_VAL_VALID) { + const char *p = math_state->lookupvar(t->var_name); if (p) { remembered_name *cur; - remembered_name cur_save; + remembered_name remember; /* did we already see this name? * testcase: a=b; b=a; echo $((a)) */ for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) { - if (strcmp(cur->var, t->var) == 0) { + if (strcmp(cur->var_name, t->var_name) == 0) { /* Yes */ return "expression recursion loop detected"; } @@ -271,9 +276,9 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) /* push current var name */ cur = math_state->list_of_recursed_names; - cur_save.var = t->var; - cur_save.next = cur; - math_state->list_of_recursed_names = &cur_save; + remember.var_name = t->var_name; + remember.next = cur; + math_state->list_of_recursed_names = &remember; /* recursively evaluate p as expression */ t->val = evaluate_string(math_state, p); @@ -286,7 +291,7 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) /* treat undefined var as 0 */ t->val = 0; } - return 0; + return NULL; } /* "Applying" a token means performing it on the top elements on the integer @@ -303,7 +308,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ /* There is no operator that can work without arguments */ if (NUMPTR == numstack) - goto err; + goto syntax_err; top_of_stack = NUMPTR - 1; @@ -326,15 +331,15 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ else if (op != TOK_UPLUS) { /* Binary operators */ arith_t right_side_val; - char bad_second_val; + int bad_second_val; /* Binary operators need two arguments */ if (top_of_stack == numstack) - goto err; + goto syntax_err; /* ...and they pop one */ NUMPTR = top_of_stack; /* this decrements NUMPTR */ - bad_second_val = top_of_stack->second_val_present; + bad_second_val = (top_of_stack->var_name == SECOND_VAL_VALID); if (op == TOK_CONDITIONAL) { /* ? operation */ /* Make next if (...) protect against * $((expr1 ? expr2)) - that is, missing ": expr" */ @@ -363,8 +368,10 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ /* Protect against $((expr : expr)) */ return "malformed ?: operator"; } - top_of_stack->second_val_present = op; + top_of_stack->val = rez; top_of_stack->second_val = right_side_val; + top_of_stack->var_name = SECOND_VAL_VALID; + return NULL; } else if (op == TOK_BOR || op == TOK_OR_ASSIGN) rez |= right_side_val; @@ -439,13 +446,13 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (is_assign_op(op)) { char buf[sizeof(arith_t)*3 + 2]; - if (top_of_stack->var == NULL) { + if (!top_of_stack->var_name || top_of_stack->var_name == SECOND_VAL_VALID) { /* Hmm, 1=2 ? */ - goto err; + goto syntax_err; } /* Save to shell variable */ sprintf(buf, ARITH_FMT, rez); - math_state->setvar(top_of_stack->var, buf); + math_state->setvar(top_of_stack->var_name, buf); /* After saving, make previous value for v++ or v-- */ if (op == TOK_POST_INC) rez--; @@ -455,9 +462,9 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ top_of_stack->val = rez; /* Erase var name, it is just a number now */ - top_of_stack->var = NULL; + top_of_stack->var_name = NULL; return NULL; - err: + syntax_err: return "arithmetic syntax error"; #undef NUMPTR } @@ -660,12 +667,11 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (p != expr) { /* Name */ size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ - numstackptr->var = alloca(var_name_size); - safe_strncpy(numstackptr->var, expr, var_name_size); + numstackptr->var_name = alloca(var_name_size); + safe_strncpy(numstackptr->var_name, expr, var_name_size); //bb_error_msg("var:'%s'", numstackptr->var); expr = p; num: - numstackptr->second_val_present = 0; numstackptr++; lasttok = TOK_NUM; continue; @@ -673,7 +679,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (isdigit(*expr)) { /* Number */ - numstackptr->var = NULL; + numstackptr->var_name = NULL; errno = 0; numstackptr->val = strto_arith_t(expr, (char**) &expr); /* A number can't be followed by another number, or a variable name. @@ -702,7 +708,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if ((expr[0] == '+' || expr[0] == '-') && (expr[1] == expr[0]) ) { - if (numstackptr == numstack || !numstackptr[-1].var) { /* not a VAR++ */ + if (numstackptr == numstack || !numstackptr[-1].var_name) { /* not a VAR++ */ char next = skip_whitespace(expr + 2)[0]; if (!(isalpha(next) || next == '_')) { /* not a ++VAR */ //bb_error_msg("special %c%c", expr[0], expr[0]); @@ -795,14 +801,14 @@ evaluate_string(arith_state_t *math_state, const char *expr) //bb_error_msg("op == TOK_RPAREN"); if (prev_op == TOK_LPAREN) { //bb_error_msg("prev_op == TOK_LPAREN"); -//bb_error_msg(" %p %p numstackptr[-1].var:'%s'", numstack, numstackptr-1, numstackptr[-1].var); - if (numstackptr[-1].var) { +//bb_error_msg(" %p %p numstackptr[-1].var_name:'%s'", numstack, numstackptr-1, numstackptr[-1].var_name); + if (numstackptr[-1].var_name && numstackptr[-1].var_name != SECOND_VAL_VALID) { /* Expression is (var), lookup now */ errmsg = arith_lookup_val(math_state, &numstackptr[-1]); if (errmsg) goto err_with_custom_msg; /* Erase var name: (var) is just a number, for example, (var) = 1 is not valid */ - numstackptr[-1].var = NULL; + numstackptr[-1].var_name = NULL; } /* Any operator directly after a * close paren should consider itself binary */ From vda.linux at googlemail.com Tue Jun 13 13:21:22 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 13 Jun 2023 15:21:22 +0200 Subject: [git commit] shell: add a few yet-failing arithmentic tests Message-ID: <20230613132259.716B782A17@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=8ccb3f7b1375862f8ef82cd9d225da986b80f054 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-assign-in-varexp.right | 3 +++ shell/ash_test/ash-arith/arith-assign-in-varexp.tests | 8 ++++++++ shell/ash_test/ash-arith/arith-comma1.right | 3 +++ shell/ash_test/ash-arith/arith-comma1.tests | 6 ++++++ shell/ash_test/ash-arith/arith-precedence1.right | 1 + shell/ash_test/ash-arith/arith-precedence1.tests | 2 ++ shell/ash_test/ash-arith/arith-ternary1.right | 5 +++++ shell/ash_test/ash-arith/arith-ternary1.tests | 12 ++++++++++++ shell/ash_test/ash-arith/arith-ternary2.right | 1 + shell/ash_test/ash-arith/arith-ternary2.tests | 2 ++ 10 files changed, 43 insertions(+) diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp.right b/shell/ash_test/ash-arith/arith-assign-in-varexp.right new file mode 100644 index 000000000..06ac80a64 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-assign-in-varexp.right @@ -0,0 +1,3 @@ +20:20 +a=b=10 +b=10 diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp.tests b/shell/ash_test/ash-arith/arith-assign-in-varexp.tests new file mode 100755 index 000000000..920aaa779 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-assign-in-varexp.tests @@ -0,0 +1,8 @@ +exec 2>&1 +a='b=10' +b=3 +# The variables should evaluate left-to-right, +# thus b is set to 10 _before_ addition +echo 20:$((a + b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/ash_test/ash-arith/arith-comma1.right b/shell/ash_test/ash-arith/arith-comma1.right new file mode 100644 index 000000000..be1264cc0 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-comma1.right @@ -0,0 +1,3 @@ +10:10 +a=b=10 +b=10 diff --git a/shell/ash_test/ash-arith/arith-comma1.tests b/shell/ash_test/ash-arith/arith-comma1.tests new file mode 100755 index 000000000..f86304303 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-comma1.tests @@ -0,0 +1,6 @@ +exec 2>&1 +a='b=10' +b=3 +echo 10:$((a,b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/ash_test/ash-arith/arith-precedence1.right b/shell/ash_test/ash-arith/arith-precedence1.right new file mode 100644 index 000000000..7f407b5d2 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-precedence1.right @@ -0,0 +1 @@ +4:4 diff --git a/shell/ash_test/ash-arith/arith-precedence1.tests b/shell/ash_test/ash-arith/arith-precedence1.tests new file mode 100755 index 000000000..964ae4ee2 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-precedence1.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 4:"$((0 ? 1,2 : 3,4))" diff --git a/shell/ash_test/ash-arith/arith-ternary1.right b/shell/ash_test/ash-arith/arith-ternary1.right new file mode 100644 index 000000000..c968f1181 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary1.right @@ -0,0 +1,5 @@ +42:42 +a=0 +6:6 +a=b=+err+ +b=6 diff --git a/shell/ash_test/ash-arith/arith-ternary1.tests b/shell/ash_test/ash-arith/arith-ternary1.tests new file mode 100755 index 000000000..5a54e34b6 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary1.tests @@ -0,0 +1,12 @@ +exec 2>&1 +a=0 +# The not-taken branch should not evaluate +echo 42:$((1 ? 42 : (a+=2))) +echo "a=$a" + +a='b=+err+' +b=5 +# The not-taken branch should not even parse variables +echo 6:$((0 ? a : ++b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/ash_test/ash-arith/arith-ternary2.right b/shell/ash_test/ash-arith/arith-ternary2.right new file mode 100644 index 000000000..aa54bd925 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary2.right @@ -0,0 +1 @@ +5:5 diff --git a/shell/ash_test/ash-arith/arith-ternary2.tests b/shell/ash_test/ash-arith/arith-ternary2.tests new file mode 100755 index 000000000..eefc8e7ce --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary2.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 5:$((1?2?3?4?5:6:7:8:9)) From vda.linux at googlemail.com Tue Jun 13 14:25:13 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 13 Jun 2023 16:25:13 +0200 Subject: [git commit] syslogd: daemonize _after_ init (so that init errors are visible, if they occur) Message-ID: <20230613142812.17AD782AE9@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=373f64eef3cc121d7531522ef654feb0a8068d5a branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- sysklogd/syslogd.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c index 20034e969..7711e467b 100644 --- a/sysklogd/syslogd.c +++ b/sysklogd/syslogd.c @@ -1055,6 +1055,14 @@ static int NOINLINE syslogd_init(char **argv) G.hostname = safe_gethostname(); *strchrnul(G.hostname, '.') = '\0'; + xmove_fd(create_socket(), STDIN_FILENO); + + if (opts & OPT_circularlog) + ipcsyslog_init(); + + if (opts & OPT_kmsg) + kmsg_init(); + if (!(opts & OPT_nofork)) { bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); } @@ -1068,14 +1076,6 @@ static int NOINLINE syslogd_init(char **argv) signal(SIGALRM, do_mark); alarm(G.markInterval); #endif - xmove_fd(create_socket(), STDIN_FILENO); - - if (opts & OPT_circularlog) - ipcsyslog_init(); - - if (opts & OPT_kmsg) - kmsg_init(); - return opts; } From vda.linux at googlemail.com Tue Jun 13 14:15:18 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 13 Jun 2023 16:15:18 +0200 Subject: [git commit] shell/math: fix one name check, other minor cleanups Message-ID: <20230613142812.04EC082AE7@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=c1d7507a4d36bbeba88b09e8f0269fe81e1cb33f branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/shell/math.c b/shell/math.c index af8e691fe..57eb56952 100644 --- a/shell/math.c +++ b/shell/math.c @@ -245,6 +245,9 @@ typedef struct { char *var_name; } var_or_num_t; +#define VALID_NAME(name) ((name) && (name) != SECOND_VAL_VALID) +#define NOT_NAME(name) (!(name) || (name) == SECOND_VAL_VALID) + typedef struct remembered_name { struct remembered_name *next; @@ -258,7 +261,7 @@ evaluate_string(arith_state_t *math_state, const char *expr); static const char* arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) { - if (t->var_name && t->var_name != SECOND_VAL_VALID) { + if (VALID_NAME(t->var_name)) { const char *p = math_state->lookupvar(t->var_name); if (p) { remembered_name *cur; @@ -275,16 +278,15 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) } /* push current var name */ - cur = math_state->list_of_recursed_names; remember.var_name = t->var_name; - remember.next = cur; + remember.next = math_state->list_of_recursed_names; math_state->list_of_recursed_names = &remember; /* recursively evaluate p as expression */ t->val = evaluate_string(math_state, p); /* pop current var name */ - math_state->list_of_recursed_names = cur; + math_state->list_of_recursed_names = remember.next; return math_state->errmsg; } @@ -446,7 +448,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (is_assign_op(op)) { char buf[sizeof(arith_t)*3 + 2]; - if (!top_of_stack->var_name || top_of_stack->var_name == SECOND_VAL_VALID) { + if (NOT_NAME(top_of_stack->var_name)) { /* Hmm, 1=2 ? */ goto syntax_err; } @@ -708,9 +710,11 @@ evaluate_string(arith_state_t *math_state, const char *expr) if ((expr[0] == '+' || expr[0] == '-') && (expr[1] == expr[0]) ) { - if (numstackptr == numstack || !numstackptr[-1].var_name) { /* not a VAR++ */ + if (numstackptr == numstack || NOT_NAME(numstackptr[-1].var_name)) { + /* not a VAR++ */ char next = skip_whitespace(expr + 2)[0]; - if (!(isalpha(next) || next == '_')) { /* not a ++VAR */ + if (!(isalpha(next) || next == '_')) { + /* not a ++VAR */ //bb_error_msg("special %c%c", expr[0], expr[0]); op = (expr[0] == '+' ? TOK_ADD : TOK_SUB); expr++; @@ -802,7 +806,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (prev_op == TOK_LPAREN) { //bb_error_msg("prev_op == TOK_LPAREN"); //bb_error_msg(" %p %p numstackptr[-1].var_name:'%s'", numstack, numstackptr-1, numstackptr[-1].var_name); - if (numstackptr[-1].var_name && numstackptr[-1].var_name != SECOND_VAL_VALID) { + if (VALID_NAME(numstackptr[-1].var_name)) { /* Expression is (var), lookup now */ errmsg = arith_lookup_val(math_state, &numstackptr[-1]); if (errmsg) From vda.linux at googlemail.com Tue Jun 13 14:24:56 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Tue, 13 Jun 2023 16:24:56 +0200 Subject: [git commit] syslogd: decrease stack usage, ~50 bytes Message-ID: <20230613142812.0EE3E82AE8@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=02378ce20c6d2df062357b6d60fc440609d203be branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta syslogd_init - 1007 +1007 syslogd_main 1619 636 -983 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 1007/-983) Total: 24 bytes Signed-off-by: Denys Vlasenko --- sysklogd/syslogd.c | 156 +++++++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c index 6ddfd771a..20034e969 100644 --- a/sysklogd/syslogd.c +++ b/sysklogd/syslogd.c @@ -1002,20 +1002,63 @@ static int try_to_resolve_remote(remoteHost_t *rh) } #endif -static void do_syslogd(void) NORETURN; -static void do_syslogd(void) +/* By doing init in a separate function we decrease stack usage + * in main loop. + */ +static int NOINLINE syslogd_init(char **argv) { + int opts; + char OPTION_DECL; #if ENABLE_FEATURE_REMOTE_LOG - llist_t *item; + llist_t *remoteAddrList = NULL; #endif -#if ENABLE_FEATURE_SYSLOGD_DUP - int last_sz = -1; - char *last_buf; - char *recvbuf = G.recvbuf; -#else -#define recvbuf (G.recvbuf) + + /* No non-option params */ + opts = getopt32(argv, "^"OPTION_STR"\0""=0", OPTION_PARAM); +#if ENABLE_FEATURE_REMOTE_LOG + while (remoteAddrList) { + remoteHost_t *rh = xzalloc(sizeof(*rh)); + rh->remoteHostname = llist_pop(&remoteAddrList); + rh->remoteFD = -1; + rh->last_dns_resolve = monotonic_sec() - DNS_WAIT_SEC - 1; + llist_add_to(&G.remoteHosts, rh); + } #endif +#ifdef SYSLOGD_MARK + if (opts & OPT_mark) // -m + G.markInterval = xatou_range(opt_m, 0, INT_MAX/60) * 60; +#endif + //if (opts & OPT_nofork) // -n + //if (opts & OPT_outfile) // -O + if (opts & OPT_loglevel) // -l + G.logLevel = xatou_range(opt_l, 1, 8); + //if (opts & OPT_small) // -S +#if ENABLE_FEATURE_ROTATE_LOGFILE + if (opts & OPT_filesize) // -s + G.logFileSize = xatou_range(opt_s, 0, INT_MAX/1024) * 1024; + if (opts & OPT_rotatecnt) // -b + G.logFileRotate = xatou_range(opt_b, 0, 99); +#endif +#if ENABLE_FEATURE_IPC_SYSLOG + if (opt_C) // -Cn + G.shm_size = xatoul_range(opt_C, 4, INT_MAX/1024) * 1024; +#endif + /* If they have not specified remote logging, then log locally */ + if (ENABLE_FEATURE_REMOTE_LOG && !(opts & OPT_remotelog)) // -R + option_mask32 |= OPT_locallog; +#if ENABLE_FEATURE_SYSLOGD_CFG + parse_syslogdcfg(opt_f); +#endif + + /* Store away localhost's name before the fork */ + G.hostname = safe_gethostname(); + *strchrnul(G.hostname, '.') = '\0'; + + if (!(opts & OPT_nofork)) { + bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); + } + /* Set up signal handlers (so that they interrupt read()) */ signal_no_SA_RESTART_empty_mask(SIGTERM, record_signo); signal_no_SA_RESTART_empty_mask(SIGINT, record_signo); @@ -1027,15 +1070,39 @@ static void do_syslogd(void) #endif xmove_fd(create_socket(), STDIN_FILENO); - if (option_mask32 & OPT_circularlog) + if (opts & OPT_circularlog) ipcsyslog_init(); - if (option_mask32 & OPT_kmsg) + if (opts & OPT_kmsg) kmsg_init(); + return opts; +} + +int syslogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int syslogd_main(int argc UNUSED_PARAM, char **argv) +{ + int opts; +#if ENABLE_FEATURE_REMOTE_LOG + llist_t *item; +#endif +#if ENABLE_FEATURE_SYSLOGD_DUP + int last_sz = -1; + char *last_buf; + char *recvbuf; +#else +#define recvbuf (G.recvbuf) +#endif + + INIT_G(); + opts = syslogd_init(argv); + timestamp_and_log_internal("syslogd started: BusyBox v" BB_VER); write_pidfile_std_path_and_ext("syslogd"); +#if ENABLE_FEATURE_SYSLOGD_DUP + recvbuf = G.recvbuf; +#endif while (!bb_got_signal) { ssize_t sz; @@ -1070,7 +1137,7 @@ static void do_syslogd(void) sz--; } #if ENABLE_FEATURE_SYSLOGD_DUP - if ((option_mask32 & OPT_dup) && (sz == last_sz)) + if ((opts & OPT_dup) && (sz == last_sz)) if (memcmp(last_buf, recvbuf, sz) == 0) continue; last_sz = sz; @@ -1111,7 +1178,7 @@ static void do_syslogd(void) } } #endif - if (!ENABLE_FEATURE_REMOTE_LOG || (option_mask32 & OPT_locallog)) { + if (!ENABLE_FEATURE_REMOTE_LOG || (opts & OPT_locallog)) { recvbuf[sz] = '\0'; /* ensure it *is* NUL terminated */ split_escape_and_log(recvbuf, sz); } @@ -1120,73 +1187,12 @@ static void do_syslogd(void) timestamp_and_log_internal("syslogd exiting"); remove_pidfile_std_path_and_ext("syslogd"); ipcsyslog_cleanup(); - if (option_mask32 & OPT_kmsg) + if (opts & OPT_kmsg) kmsg_cleanup(); kill_myself_with_sig(bb_got_signal); #undef recvbuf } -int syslogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; -int syslogd_main(int argc UNUSED_PARAM, char **argv) -{ - int opts; - char OPTION_DECL; -#if ENABLE_FEATURE_REMOTE_LOG - llist_t *remoteAddrList = NULL; -#endif - - INIT_G(); - - /* No non-option params */ - opts = getopt32(argv, "^"OPTION_STR"\0""=0", OPTION_PARAM); -#if ENABLE_FEATURE_REMOTE_LOG - while (remoteAddrList) { - remoteHost_t *rh = xzalloc(sizeof(*rh)); - rh->remoteHostname = llist_pop(&remoteAddrList); - rh->remoteFD = -1; - rh->last_dns_resolve = monotonic_sec() - DNS_WAIT_SEC - 1; - llist_add_to(&G.remoteHosts, rh); - } -#endif - -#ifdef SYSLOGD_MARK - if (opts & OPT_mark) // -m - G.markInterval = xatou_range(opt_m, 0, INT_MAX/60) * 60; -#endif - //if (opts & OPT_nofork) // -n - //if (opts & OPT_outfile) // -O - if (opts & OPT_loglevel) // -l - G.logLevel = xatou_range(opt_l, 1, 8); - //if (opts & OPT_small) // -S -#if ENABLE_FEATURE_ROTATE_LOGFILE - if (opts & OPT_filesize) // -s - G.logFileSize = xatou_range(opt_s, 0, INT_MAX/1024) * 1024; - if (opts & OPT_rotatecnt) // -b - G.logFileRotate = xatou_range(opt_b, 0, 99); -#endif -#if ENABLE_FEATURE_IPC_SYSLOG - if (opt_C) // -Cn - G.shm_size = xatoul_range(opt_C, 4, INT_MAX/1024) * 1024; -#endif - /* If they have not specified remote logging, then log locally */ - if (ENABLE_FEATURE_REMOTE_LOG && !(opts & OPT_remotelog)) // -R - option_mask32 |= OPT_locallog; -#if ENABLE_FEATURE_SYSLOGD_CFG - parse_syslogdcfg(opt_f); -#endif - - /* Store away localhost's name before the fork */ - G.hostname = safe_gethostname(); - *strchrnul(G.hostname, '.') = '\0'; - - if (!(opts & OPT_nofork)) { - bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); - } - - do_syslogd(); - /* return EXIT_SUCCESS; */ -} - /* Clean up. Needed because we are included from syslogd_and_logger.c */ #undef DEBUG #undef SYSLOGD_MARK From vda.linux at googlemail.com Tue Jun 13 23:05:40 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 01:05:40 +0200 Subject: [git commit] shell/math: remove a redundant check Message-ID: <20230613230624.BC6878344C@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=a02450ff0bfa45618e72fc7103ea3a8f0e7fff80 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1134 1087 -47 Signed-off-by: Denys Vlasenko --- shell/math.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/math.c b/shell/math.c index d5f3ce361..9ca7c6bb1 100644 --- a/shell/math.c +++ b/shell/math.c @@ -435,10 +435,11 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ c *= rez; rez = c; } - else if (right_side_val == 0) - return "divide by zero"; - else if (op == TOK_DIV || op == TOK_DIV_ASSIGN - || op == TOK_REM || op == TOK_REM_ASSIGN) { + else /*if (op == TOK_DIV || op == TOK_DIV_ASSIGN + || op == TOK_REM || op == TOK_REM_ASSIGN) - always true */ + { + if (right_side_val == 0) + return "divide by zero"; /* * bash 4.2.45 x86 64bit: SEGV on 'echo $((2**63 / -1))' * @@ -456,9 +457,8 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ } if (op == TOK_DIV || op == TOK_DIV_ASSIGN) rez /= right_side_val; - else { + else rez %= right_side_val; - } } } From vda.linux at googlemail.com Tue Jun 13 22:41:18 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 00:41:18 +0200 Subject: [git commit] shell/math: document ternary ?: op's weirdness, add code comments Message-ID: <20230613230624.B319E83442@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=8acbf31708779e7ad559775c9db4ebd7a962be33 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-precedence1.right | 3 + shell/ash_test/ash-arith/arith-precedence1.tests | 13 ++++ shell/math.c | 77 +++++++++++++++--------- 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-precedence1.right b/shell/ash_test/ash-arith/arith-precedence1.right index 7f407b5d2..3f9320a13 100644 --- a/shell/ash_test/ash-arith/arith-precedence1.right +++ b/shell/ash_test/ash-arith/arith-precedence1.right @@ -1 +1,4 @@ 4:4 +4:4 +4:4 +4:4 diff --git a/shell/ash_test/ash-arith/arith-precedence1.tests b/shell/ash_test/ash-arith/arith-precedence1.tests index 964ae4ee2..bfef05292 100755 --- a/shell/ash_test/ash-arith/arith-precedence1.tests +++ b/shell/ash_test/ash-arith/arith-precedence1.tests @@ -1,2 +1,15 @@ exec 2>&1 +# bash documentation says that precedence order is: +# ... +# expr ? expr1 : expr2 +# = *= /= %= += -= <<= >>= &= ^= |= +# exprA , exprB +# but in practice, the rules for expr1 and expr2 are different: +# assignments and commas in expr1 have higher precedence than :?, +# but in expr2 they haven't: +# "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4" +# "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error) +echo 4:$((0 ? 1,2 : 3,4)) +echo 4:$((1 ? 1,2 : 3,4)) echo 4:"$((0 ? 1,2 : 3,4))" +echo 4:"$((1 ? 1,2 : 3,4))" diff --git a/shell/math.c b/shell/math.c index 57eb56952..d5f3ce361 100644 --- a/shell/math.c +++ b/shell/math.c @@ -116,6 +116,12 @@ #include "libbb.h" #include "math.h" +#if 1 +# define dbg(...) ((void)0) +#else +# define dbg(...) bb_error_msg(__VA_ARGS__) +#endif + typedef unsigned char operator; /* An operator's token id is a bit of a bitfield. The lower 5 bits are the @@ -151,6 +157,17 @@ typedef unsigned char operator; #define fix_assignment_prec(prec) do { if (prec == 3) prec = 2; } while (0) /* Ternary conditional operator is right associative too */ +// FIXME: +// bash documentation says that precedence order is: +// ... +// expr ? expr1 : expr2 +// = *= /= %= += -= <<= >>= &= ^= |= +// exprA , exprB +// but in practice, the rules for expr1 and expr2 are different: +// assignments and commas in expr1 have higher precedence than ?:, +// but in expr2 they haven't: +// "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4" +// "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error) #define TOK_CONDITIONAL tok_decl(4,0) #define TOK_CONDITIONAL_SEP tok_decl(4,1) @@ -246,7 +263,7 @@ typedef struct { } var_or_num_t; #define VALID_NAME(name) ((name) && (name) != SECOND_VAL_VALID) -#define NOT_NAME(name) (!(name) || (name) == SECOND_VAL_VALID) +#define NOT_NAME(name) (!(name) || (name) == SECOND_VAL_VALID) typedef struct remembered_name { @@ -624,11 +641,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) var_or_num_t *const numstack = alloca((expr_len / 2) * sizeof(numstack[0])); var_or_num_t *numstackptr = numstack; /* Stack of operator tokens */ - operator *const stack = alloca(expr_len * sizeof(stack[0])); - operator *stackptr = stack; + operator *const opstack = alloca(expr_len * sizeof(opstack[0])); + operator *opstackptr = opstack; /* Start with a left paren */ - *stackptr++ = lasttok = TOK_LPAREN; + dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack)); + *opstackptr++ = lasttok = TOK_LPAREN; errmsg = NULL; while (1) { @@ -655,11 +673,11 @@ evaluate_string(arith_state_t *math_state, const char *expr) * and let the loop process it */ expr = ptr_to_rparen; //bb_error_msg("expr=')'"); - continue; + goto tok_find; } /* At this point, we're done with the expression */ if (numstackptr != numstack + 1) { - /* ...but if there isn't, it's bad */ + /* if there is not exactly one result, it's bad */ goto err; } goto ret; @@ -671,9 +689,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ numstackptr->var_name = alloca(var_name_size); safe_strncpy(numstackptr->var_name, expr, var_name_size); -//bb_error_msg("var:'%s'", numstackptr->var); + dbg("[%d] var:'%s'", (int)(numstackptr - numstack), numstackptr->var_name); expr = p; - num: + push_num: numstackptr++; lasttok = TOK_NUM; continue; @@ -684,6 +702,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) numstackptr->var_name = NULL; errno = 0; numstackptr->val = strto_arith_t(expr, (char**) &expr); + dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); /* A number can't be followed by another number, or a variable name. * We'd catch this later anyway, but this would require numstack[] * to be twice as deep to handle strings where _every_ char is @@ -691,10 +710,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) */ if (isalnum(*expr) || *expr == '_') goto err; -//bb_error_msg("val:%lld", numstackptr->val); if (errno) numstackptr->val = 0; /* bash compat */ - goto num; + goto push_num; } /* Should be an operator */ @@ -715,14 +733,13 @@ evaluate_string(arith_state_t *math_state, const char *expr) char next = skip_whitespace(expr + 2)[0]; if (!(isalpha(next) || next == '_')) { /* not a ++VAR */ - //bb_error_msg("special %c%c", expr[0], expr[0]); op = (expr[0] == '+' ? TOK_ADD : TOK_SUB); expr++; goto tok_found1; } } } - + tok_find: p = op_tokens; while (1) { /* Compare expr to current op_tokens[] element */ @@ -799,13 +816,10 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* The algorithm employed here is simple: while we don't * hit an open paren nor the bottom of the stack, pop * tokens and apply them */ - while (stackptr != stack) { - operator prev_op = *--stackptr; + while (opstackptr != opstack) { + operator prev_op = *--opstackptr; if (op == TOK_RPAREN) { -//bb_error_msg("op == TOK_RPAREN"); if (prev_op == TOK_LPAREN) { -//bb_error_msg("prev_op == TOK_LPAREN"); -//bb_error_msg(" %p %p numstackptr[-1].var_name:'%s'", numstack, numstackptr-1, numstackptr[-1].var_name); if (VALID_NAME(numstackptr[-1].var_name)) { /* Expression is (var), lookup now */ errmsg = arith_lookup_val(math_state, &numstackptr[-1]); @@ -819,31 +833,38 @@ evaluate_string(arith_state_t *math_state, const char *expr) lasttok = TOK_NUM; goto next; } -//bb_error_msg("prev_op != TOK_LPAREN"); + /* Not (y), but ...x~y): fall through to evaluate x~y */ } else { operator prev_prec = PREC(prev_op); -//bb_error_msg("op != TOK_RPAREN"); fix_assignment_prec(prec); fix_assignment_prec(prev_prec); if (prev_prec < prec || (prev_prec == prec && is_right_associative(prec)) ) { - stackptr++; - break; + /* ...x~y@: push @ on opstack */ + opstackptr++; /* undo removal of ~ op */ + goto push_op; } + /* ...x~y@: evaluate x~y, replace it on stack with result. Then repeat */ } -//bb_error_msg("arith_apply(prev_op:%02x)", prev_op); + dbg("arith_apply(prev_op:%02x, numstack:%d)", prev_op, (int)(numstackptr - numstack)); errmsg = arith_apply(math_state, prev_op, numstack, &numstackptr); if (errmsg) goto err_with_custom_msg; - } - if (op == TOK_RPAREN) +dbg(" numstack:%d val:%lld %lld %p", (int)(numstackptr - numstack), + numstackptr[-1].val, + numstackptr[-1].var_name == SECOND_VAL_VALID ? numstackptr[-1].second_val : 0, + numstackptr[-1].var_name +); + } /* while (opstack not empty) */ + if (op == TOK_RPAREN) /* unpaired RPAREN? */ goto err; } - - /* Push this operator to the stack and remember it */ -//bb_error_msg("push op:%02x", op); - *stackptr++ = lasttok = op; + /* else: LPAREN or UNARY: push it on opstack */ + push_op: + /* Push this operator to opstack */ + dbg("(%d) op:%02x", (int)(opstackptr - opstack), op); + *opstackptr++ = lasttok = op; next: ; } /* while (1) */ From vda.linux at googlemail.com Wed Jun 14 09:10:45 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 11:10:45 +0200 Subject: [git commit] shell/math: remove now-unused second_val Message-ID: <20230614102428.0D3FE835A4@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=5febdb122357dbe39e236c9e93d06dab328edb45 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1137 1134 -3 evaluate_string 1101 1095 -6 arith_lookup_val 150 143 -7 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/3 up/down: 0/-16) Total: -16 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/shell/math.c b/shell/math.c index 2b7a3494f..fa22bc400 100644 --- a/shell/math.c +++ b/shell/math.c @@ -246,24 +246,11 @@ is_right_associative(operator prec) typedef struct { arith_t val; - /* We acquire second_val only when "expr1 : expr2" part - * of ternary ?: op is evaluated. - * We treat ?: as two binary ops: (expr ? (expr1 : expr2)). - * ':' produces a new value which has two parts, val and second_val; - * then '?' selects one of them based on its left side. - */ - arith_t second_val; -#define SECOND_VAL_VALID ((char*)(intptr_t)-1) - /* If NULL then it's just a number, if SECOND_VAL_VALID, - * it's a result of "expr : expr", else it's a named variable. - * (We use SECOND_VAL_VALID instead of a bit flag to keep - * var_or_num_t smaller, we allocate a lot of them on stack). - */ char *var_name; } var_or_num_t; -#define VALID_NAME(name) ((name) && (name) != SECOND_VAL_VALID) -#define NOT_NAME(name) (!(name) || (name) == SECOND_VAL_VALID) +#define VALID_NAME(name) (name) +#define NOT_NAME(name) (!(name)) typedef struct remembered_name { @@ -854,11 +841,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) errmsg = arith_apply(math_state, prev_op, numstack, &numstackptr); if (errmsg) goto err_with_custom_msg; -dbg(" numstack:%d val:%lld %lld %p", (int)(numstackptr - numstack), - numstackptr[-1].val, - numstackptr[-1].var_name == SECOND_VAL_VALID ? numstackptr[-1].second_val : 0, - numstackptr[-1].var_name -); +dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[-1].val, numstackptr[-1].var_name); /* For ternary ?: we need to remove ? from opstack too, not just : */ if (prev_op == TOK_CONDITIONAL_SEP) { // This is caught in arith_apply() From vda.linux at googlemail.com Wed Jun 14 09:07:30 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 11:07:30 +0200 Subject: [git commit] shell/math: fix nested ?: and do not parse variables in not-taken branch Message-ID: <20230614102428.033FF835A0@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=46dccd2ec0eafd850b2168d4dfe4e74949fd3424 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Fixes arith-ternary1.tests and arith-ternary_nested.tests function old new delta evaluate_string 1043 1101 +58 arith_apply 1087 1137 +50 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 108/0) Total: 108 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-ternary1.right | 3 - shell/ash_test/ash-arith/arith-ternary1.tests | 7 -- shell/ash_test/ash-arith/arith-ternary2.right | 4 +- shell/ash_test/ash-arith/arith-ternary2.tests | 7 +- ...h-ternary2.right => arith-ternary_nested.right} | 0 ...h-ternary2.tests => arith-ternary_nested.tests} | 0 shell/math.c | 80 +++++++++++++--------- 7 files changed, 58 insertions(+), 43 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-ternary1.right b/shell/ash_test/ash-arith/arith-ternary1.right index c968f1181..6b751d7b8 100644 --- a/shell/ash_test/ash-arith/arith-ternary1.right +++ b/shell/ash_test/ash-arith/arith-ternary1.right @@ -1,5 +1,2 @@ 42:42 a=0 -6:6 -a=b=+err+ -b=6 diff --git a/shell/ash_test/ash-arith/arith-ternary1.tests b/shell/ash_test/ash-arith/arith-ternary1.tests index 5a54e34b6..3532ce54d 100755 --- a/shell/ash_test/ash-arith/arith-ternary1.tests +++ b/shell/ash_test/ash-arith/arith-ternary1.tests @@ -3,10 +3,3 @@ a=0 # The not-taken branch should not evaluate echo 42:$((1 ? 42 : (a+=2))) echo "a=$a" - -a='b=+err+' -b=5 -# The not-taken branch should not even parse variables -echo 6:$((0 ? a : ++b)) -echo "a=$a" -echo "b=$b" diff --git a/shell/ash_test/ash-arith/arith-ternary2.right b/shell/ash_test/ash-arith/arith-ternary2.right index aa54bd925..a549b1b5c 100644 --- a/shell/ash_test/ash-arith/arith-ternary2.right +++ b/shell/ash_test/ash-arith/arith-ternary2.right @@ -1 +1,3 @@ -5:5 +6:6 +a=b=+err+ +b=6 diff --git a/shell/ash_test/ash-arith/arith-ternary2.tests b/shell/ash_test/ash-arith/arith-ternary2.tests index eefc8e7ce..cb3163932 100755 --- a/shell/ash_test/ash-arith/arith-ternary2.tests +++ b/shell/ash_test/ash-arith/arith-ternary2.tests @@ -1,2 +1,7 @@ exec 2>&1 -echo 5:$((1?2?3?4?5:6:7:8:9)) +a='b=+err+' +b=5 +# The not-taken branch should not parse variables +echo 6:$((0 ? a : ++b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/ash_test/ash-arith/arith-ternary2.right b/shell/ash_test/ash-arith/arith-ternary_nested.right similarity index 100% copy from shell/ash_test/ash-arith/arith-ternary2.right copy to shell/ash_test/ash-arith/arith-ternary_nested.right diff --git a/shell/ash_test/ash-arith/arith-ternary2.tests b/shell/ash_test/ash-arith/arith-ternary_nested.tests similarity index 100% copy from shell/ash_test/ash-arith/arith-ternary2.tests copy to shell/ash_test/ash-arith/arith-ternary_nested.tests diff --git a/shell/math.c b/shell/math.c index 9ca7c6bb1..2b7a3494f 100644 --- a/shell/math.c +++ b/shell/math.c @@ -331,6 +331,28 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ top_of_stack = NUMPTR - 1; + if (op == TOK_CONDITIONAL_SEP) { + /* "expr1 ? expr2 : expr3" operation */ + var_or_num_t *expr1 = &top_of_stack[-2]; + if (expr1 < numstack) { + return "malformed ?: operator"; + } + err = arith_lookup_val(math_state, expr1); + if (err) + return err; + if (expr1->val != 0) /* select expr2 or expr3 */ + top_of_stack--; + err = arith_lookup_val(math_state, top_of_stack); + if (err) + return err; + NUMPTR = expr1 + 1; + expr1->val = top_of_stack->val; + expr1->var_name = NULL; + return NULL; + } + if (op == TOK_CONDITIONAL) /* Example: $((a ? b)) */ + return "malformed ?: operator"; + /* Resolve name to value, if needed */ err = arith_lookup_val(math_state, top_of_stack); if (err) @@ -350,25 +372,12 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ else if (op != TOK_UPLUS) { /* Binary operators */ arith_t right_side_val; - int bad_second_val; /* Binary operators need two arguments */ if (top_of_stack == numstack) goto syntax_err; /* ...and they pop one */ NUMPTR = top_of_stack; /* this decrements NUMPTR */ - - bad_second_val = (top_of_stack->var_name == SECOND_VAL_VALID); - if (op == TOK_CONDITIONAL) { /* ? operation */ - /* Make next if (...) protect against - * $((expr1 ? expr2)) - that is, missing ": expr" */ - bad_second_val = !bad_second_val; - } - if (bad_second_val) { - /* Protect against $((expr expr1 : expr2)) */ - return "malformed ?: operator"; - } - top_of_stack--; /* now points to left side */ if (op != TOK_ASSIGN) { @@ -380,19 +389,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ right_side_val = rez; rez = top_of_stack->val; - if (op == TOK_CONDITIONAL) /* ? operation */ - rez = (rez ? right_side_val : top_of_stack[1].second_val); - else if (op == TOK_CONDITIONAL_SEP) { /* : operation */ - if (top_of_stack == numstack) { - /* Protect against $((expr : expr)) */ - return "malformed ?: operator"; - } - top_of_stack->val = rez; - top_of_stack->second_val = right_side_val; - top_of_stack->var_name = SECOND_VAL_VALID; - return NULL; - } - else if (op == TOK_BOR || op == TOK_OR_ASSIGN) + if (op == TOK_BOR || op == TOK_OR_ASSIGN) rez |= right_side_val; else if (op == TOK_OR) rez = right_side_val || rez; @@ -833,7 +830,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) lasttok = TOK_NUM; goto next; } - /* Not (y), but ...x~y): fall through to evaluate x~y */ + /* Not (y), but ...x~y). Fall through to evaluate x~y */ } else { operator prev_prec = PREC(prev_op); fix_assignment_prec(prec); @@ -841,11 +838,17 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (prev_prec < prec || (prev_prec == prec && is_right_associative(prec)) ) { - /* ...x~y@: push @ on opstack */ - opstackptr++; /* undo removal of ~ op */ - goto push_op; + /* Unless a?b?c:d:... and we are at the second : */ + if (op != TOK_CONDITIONAL_SEP + || prev_op != TOK_CONDITIONAL_SEP + ) { + /* ...x~y@: push @ on opstack */ + opstackptr++; /* undo removal of ~ op */ + goto push_op; + } + /* else: a?b?c:d:. Evaluate b?c:d, replace it on stack with result. Then repeat */ } - /* ...x~y@: evaluate x~y, replace it on stack with result. Then repeat */ + /* else: ...x~y at . Evaluate x~y, replace it on stack with result. Then repeat */ } dbg("arith_apply(prev_op:%02x, numstack:%d)", prev_op, (int)(numstackptr - numstack)); errmsg = arith_apply(math_state, prev_op, numstack, &numstackptr); @@ -856,6 +859,21 @@ dbg(" numstack:%d val:%lld %lld %p", (int)(numstackptr - numstack), numstackptr[-1].var_name == SECOND_VAL_VALID ? numstackptr[-1].second_val : 0, numstackptr[-1].var_name ); + /* For ternary ?: we need to remove ? from opstack too, not just : */ + if (prev_op == TOK_CONDITIONAL_SEP) { + // This is caught in arith_apply() + //if (opstackptr == opstack) { + // /* Example: $((2:3)) */ + // errmsg = "where is your ? in ?:"; + // goto err_with_custom_msg; + //} + opstackptr--; + if (*opstackptr != TOK_CONDITIONAL) { + /* Example: $((1,2:3)) */ + errmsg = "malformed ?: operator"; + goto err_with_custom_msg; + } + } } /* while (opstack not empty) */ if (op == TOK_RPAREN) /* unpaired RPAREN? */ goto err; From vda.linux at googlemail.com Wed Jun 14 09:33:59 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 11:33:59 +0200 Subject: [git commit] shell/math: fix the order of variable resolution in binops Message-ID: <20230614102428.184C7835A5@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=3df885abe340c5feaed212536139ee24d60e40a2 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1134 1143 +9 Signed-off-by: Denys Vlasenko --- .../hush-arith/arith-assign-in-varexp.right | 3 +++ .../hush-arith/arith-assign-in-varexp.tests | 8 +++++++ shell/hush_test/hush-arith/arith-comma1.right | 3 +++ shell/hush_test/hush-arith/arith-comma1.tests | 6 +++++ shell/hush_test/hush-arith/arith-ternary2.right | 3 +++ shell/hush_test/hush-arith/arith-ternary2.tests | 7 ++++++ .../hush-arith/arith-ternary_nested.right | 1 + .../hush-arith/arith-ternary_nested.tests | 2 ++ shell/math.c | 27 ++++++++++++---------- 9 files changed, 48 insertions(+), 12 deletions(-) diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp.right b/shell/hush_test/hush-arith/arith-assign-in-varexp.right new file mode 100644 index 000000000..06ac80a64 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-assign-in-varexp.right @@ -0,0 +1,3 @@ +20:20 +a=b=10 +b=10 diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp.tests b/shell/hush_test/hush-arith/arith-assign-in-varexp.tests new file mode 100755 index 000000000..920aaa779 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-assign-in-varexp.tests @@ -0,0 +1,8 @@ +exec 2>&1 +a='b=10' +b=3 +# The variables should evaluate left-to-right, +# thus b is set to 10 _before_ addition +echo 20:$((a + b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/hush_test/hush-arith/arith-comma1.right b/shell/hush_test/hush-arith/arith-comma1.right new file mode 100644 index 000000000..be1264cc0 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-comma1.right @@ -0,0 +1,3 @@ +10:10 +a=b=10 +b=10 diff --git a/shell/hush_test/hush-arith/arith-comma1.tests b/shell/hush_test/hush-arith/arith-comma1.tests new file mode 100755 index 000000000..f86304303 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-comma1.tests @@ -0,0 +1,6 @@ +exec 2>&1 +a='b=10' +b=3 +echo 10:$((a,b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/hush_test/hush-arith/arith-ternary2.right b/shell/hush_test/hush-arith/arith-ternary2.right new file mode 100644 index 000000000..a549b1b5c --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary2.right @@ -0,0 +1,3 @@ +6:6 +a=b=+err+ +b=6 diff --git a/shell/hush_test/hush-arith/arith-ternary2.tests b/shell/hush_test/hush-arith/arith-ternary2.tests new file mode 100755 index 000000000..cb3163932 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary2.tests @@ -0,0 +1,7 @@ +exec 2>&1 +a='b=+err+' +b=5 +# The not-taken branch should not parse variables +echo 6:$((0 ? a : ++b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/hush_test/hush-arith/arith-ternary_nested.right b/shell/hush_test/hush-arith/arith-ternary_nested.right new file mode 100644 index 000000000..aa54bd925 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested.right @@ -0,0 +1 @@ +5:5 diff --git a/shell/hush_test/hush-arith/arith-ternary_nested.tests b/shell/hush_test/hush-arith/arith-ternary_nested.tests new file mode 100755 index 000000000..eefc8e7ce --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 5:$((1?2?3?4?5:6:7:8:9)) diff --git a/shell/math.c b/shell/math.c index fa22bc400..077aba848 100644 --- a/shell/math.c +++ b/shell/math.c @@ -340,7 +340,20 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (op == TOK_CONDITIONAL) /* Example: $((a ? b)) */ return "malformed ?: operator"; - /* Resolve name to value, if needed */ + if (PREC(op) < UNARYPREC) { + /* In binops a ~ b, variables are resolved left-to-right, + * resolve top_of_stack[-1] _before_ resolving top_of_stack[0] + */ + if (top_of_stack == numstack) /* need two arguments */ + goto syntax_err; + /* Unless it is =, resolve top_of_stack[-1] name to value */ + if (op != TOK_ASSIGN) { + err = arith_lookup_val(math_state, top_of_stack - 1); + if (err) + return err; + } + } + /* Resolve top_of_stack[0] name to value */ err = arith_lookup_val(math_state, top_of_stack); if (err) return err; @@ -360,20 +373,10 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ /* Binary operators */ arith_t right_side_val; - /* Binary operators need two arguments */ - if (top_of_stack == numstack) - goto syntax_err; - /* ...and they pop one */ + /* Pop numstack */ NUMPTR = top_of_stack; /* this decrements NUMPTR */ top_of_stack--; /* now points to left side */ - if (op != TOK_ASSIGN) { - /* Resolve left side value (unless the op is '=') */ - err = arith_lookup_val(math_state, top_of_stack); - if (err) - return err; - } - right_side_val = rez; rez = top_of_stack->val; if (op == TOK_BOR || op == TOK_OR_ASSIGN) From vda.linux at googlemail.com Wed Jun 14 11:59:11 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 13:59:11 +0200 Subject: [git commit] shell/math: trivial code shrink Message-ID: <20230614115941.ED8A283668@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=66139330fc09384f2ce95e60ea1f5268badbafc9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1143 1132 -11 Signed-off-by: Denys Vlasenko --- shell/math.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shell/math.c b/shell/math.c index 077aba848..c1bf324f8 100644 --- a/shell/math.c +++ b/shell/math.c @@ -321,9 +321,9 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (op == TOK_CONDITIONAL_SEP) { /* "expr1 ? expr2 : expr3" operation */ var_or_num_t *expr1 = &top_of_stack[-2]; - if (expr1 < numstack) { + NUMPTR = expr1 + 1; + if (expr1 < numstack) /* Example: $((2:3)) */ return "malformed ?: operator"; - } err = arith_lookup_val(math_state, expr1); if (err) return err; @@ -332,7 +332,6 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ err = arith_lookup_val(math_state, top_of_stack); if (err) return err; - NUMPTR = expr1 + 1; expr1->val = top_of_stack->val; expr1->var_name = NULL; return NULL; @@ -343,7 +342,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (PREC(op) < UNARYPREC) { /* In binops a ~ b, variables are resolved left-to-right, * resolve top_of_stack[-1] _before_ resolving top_of_stack[0] - */ + */ if (top_of_stack == numstack) /* need two arguments */ goto syntax_err; /* Unless it is =, resolve top_of_stack[-1] name to value */ From vda.linux at googlemail.com Wed Jun 14 13:19:02 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 14 Jun 2023 15:19:02 +0200 Subject: [git commit] shell: sync ash/hush test scripts Message-ID: <20230614140927.BC282836C7@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=2ff01bb699d80cb7d24a93e812cc91c54be5cc20 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/ash_test/run-all | 21 +++++++++++++++++---- shell/hush_test/run-all | 12 ++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all index 96703ef12..066327dc0 100755 --- a/shell/ash_test/run-all +++ b/shell/ash_test/run-all @@ -47,6 +47,7 @@ do_test() # $1 but with / replaced by # so that it can be used as filename part noslash=`echo "$1" | sed 's:/:#:g'` ( + tret=0 cd "$1" || { echo "cannot cd $1!"; exit 1; } for x in run-*; do test -f "$x" || continue @@ -69,13 +70,25 @@ do_test() test -f "$name.right" || continue # echo Running test: "$x" echo -n "$1/$x:" - { + ( "$THIS_SH" "./$x" 2>&1 | \ - grep -va "^ash: using fallback suid method$" >"$name.xx" + grep -va "^ash: using fallback suid method$" >"$name.xx" + r=$? + # filter C library differences + sed -i \ + -e "/: invalid option /s:'::g" \ + "$name.xx" + test $r -eq 77 && rm -f "$TOPDIR/$noslash-$x.fail" && exit 77 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \ && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail" - } && echo " ok" || echo " fail" + ) + case $? in + 0) echo " ok";; + 77) echo " skip (feature disabled)";; + *) echo " fail ($?)"; tret=1;; + esac done + exit $tret ) } @@ -103,4 +116,4 @@ else done fi -exit ${ret} +exit $ret diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all index 3fbc7c531..7345fee43 100755 --- a/shell/hush_test/run-all +++ b/shell/hush_test/run-all @@ -29,7 +29,7 @@ fi eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config) -PATH="`pwd`:$PATH" # for hush and recho/zecho/printenv +PATH="`pwd`:$PATH" # for hush export PATH THIS_SH="`pwd`/hush" @@ -67,7 +67,8 @@ do_test() # echo Running test: "$x" echo -n "$1/$x:" ( - "$THIS_SH" "./$x" >"$name.xx" 2>&1 + "$THIS_SH" "./$x" 2>&1 | \ + grep -va "^hush: using fallback suid method$" >"$name.xx" r=$? # filter C library differences sed -i \ @@ -83,7 +84,7 @@ do_test() *) echo " fail ($?)"; tret=1;; esac done - exit ${tret} + exit $tret ) } @@ -95,6 +96,9 @@ ret=0 if [ $# -lt 1 ]; then # All sub directories modules=`ls -d hush-*` + # If you want to test hush against ash testsuite + # (have to copy ash_test dir to current dir first): + #modules=`ls -d hush-* ash_test/ash-*` for module in $modules; do do_test $module || ret=1 @@ -108,4 +112,4 @@ else done fi -exit ${ret} +exit $ret From vda.linux at googlemail.com Thu Jun 15 07:19:48 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 09:19:48 +0200 Subject: [git commit] shell/math: simpler insertion of "fake" last RPAREN Message-ID: <20230615092239.ECC2C83849@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=3829d8b6758439251fc3e34dcedf5910d039b07d branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Skip one pass through token table, since we know the result. function old new delta evaluate_string 1095 1097 +2 Signed-off-by: Denys Vlasenko --- shell/math.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shell/math.c b/shell/math.c index c1bf324f8..748c3b3ad 100644 --- a/shell/math.c +++ b/shell/math.c @@ -518,7 +518,7 @@ static const char op_tokens[] ALIGN1 = { '(', 0, TOK_LPAREN, 0 }; -#define ptr_to_rparen (&op_tokens[sizeof(op_tokens)-7]) +#define END_POINTER (&op_tokens[sizeof(op_tokens)-1]) #if ENABLE_FEATURE_SH_MATH_BASE static arith_t strto_arith_t(const char *nptr, char **endptr) @@ -653,13 +653,13 @@ evaluate_string(arith_state_t *math_state, const char *expr) * are to be applied in order. At the end, there should be a final * result on the integer stack */ - if (expr != ptr_to_rparen + 1) { + if (expr != END_POINTER) { /* If we haven't done so already, * append a closing right paren * and let the loop process it */ - expr = ptr_to_rparen; -//bb_error_msg("expr=')'"); - goto tok_find; + expr = END_POINTER; + op = TOK_RPAREN; + goto tok_found1; } /* At this point, we're done with the expression */ if (numstackptr != numstack + 1) { @@ -725,7 +725,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) } } } - tok_find: + p = op_tokens; while (1) { /* Compare expr to current op_tokens[] element */ @@ -792,7 +792,6 @@ evaluate_string(arith_state_t *math_state, const char *expr) * "applied" in this way. */ prec = PREC(op); -//bb_error_msg("prec:%02x", prec); if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { /* not left paren or unary */ if (lasttok != TOK_NUM) { From vda.linux at googlemail.com Thu Jun 15 09:22:13 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 11:22:13 +0200 Subject: [git commit] shell/math: remove special code to handle a?b?c:d:e, it works without it now Message-ID: <20230615092240.1647883853@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=61a4959251667751e424e600c6cb75de39d6b1c3 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master The "hack" to virtually parenthesize ? EXPR : made this unnecessary. The expression is effectively a?(b?(c):d):e and thus b?c:d is evaluated before continuing with the second : function old new delta evaluate_string 1148 1132 -16 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-ternary_nested2.right | 1 + shell/ash_test/ash-arith/arith-ternary_nested2.tests | 2 ++ shell/hush_test/hush-arith/arith-ternary_nested2.right | 1 + shell/hush_test/hush-arith/arith-ternary_nested2.tests | 2 ++ shell/math.c | 12 +++--------- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-ternary_nested2.right b/shell/ash_test/ash-arith/arith-ternary_nested2.right new file mode 100644 index 000000000..d80319695 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested2.right @@ -0,0 +1 @@ +3:3 diff --git a/shell/ash_test/ash-arith/arith-ternary_nested2.tests b/shell/ash_test/ash-arith/arith-ternary_nested2.tests new file mode 100755 index 000000000..e8b8a9e1a --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested2.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 3:$((0?1:2?3:4?5:6?7:8)) diff --git a/shell/hush_test/hush-arith/arith-ternary_nested2.right b/shell/hush_test/hush-arith/arith-ternary_nested2.right new file mode 100644 index 000000000..d80319695 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested2.right @@ -0,0 +1 @@ +3:3 diff --git a/shell/hush_test/hush-arith/arith-ternary_nested2.tests b/shell/hush_test/hush-arith/arith-ternary_nested2.tests new file mode 100755 index 000000000..e8b8a9e1a --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested2.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 3:$((0?1:2?3:4?5:6?7:8)) diff --git a/shell/math.c b/shell/math.c index f6aa02ac2..8d0c9dea7 100644 --- a/shell/math.c +++ b/shell/math.c @@ -840,15 +840,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (prev_prec < prec || (prev_prec == prec && is_right_associative(prec)) ) { - /* Unless a?b?c:d:... and we are at the second : */ - if (op != TOK_CONDITIONAL_SEP - || prev_op != TOK_CONDITIONAL_SEP - ) { - /* ...x~y@: push @ on opstack */ - opstackptr++; /* undo removal of ~ op */ - goto push_op; - } - /* else: a?b?c:d:. Evaluate b?c:d, replace it on stack with result. Then repeat */ + /* ...x~y@: push @ on opstack */ + opstackptr++; /* undo removal of ~ op */ + goto push_op; } /* else: ...x~y at . Evaluate x~y, replace it on stack with result. Then repeat */ } From vda.linux at googlemail.com Thu Jun 15 08:14:43 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 10:14:43 +0200 Subject: [git commit] shell/math: fix parsing of ?: and explain why it's parsed that way Message-ID: <20230615092240.0867A83850@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=5f56a0388271d2de6cf31af1041bdcb3d11029fc branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master This fixes arith-precedence1.tests. This breaks arith-ternary2.tests again (we now evaluate variables on not-taken branches). We need a better logic here anyway: not only bare variables should not evaluate when not-taken: 1 ? eval_me : do_not_eval but any (arbitrarily complex) expressions shouldn't evaluate as well! 1 ? var_is_set=1 : ((var_is_not_set=2,var2*=4)) function old new delta evaluate_string 1097 1148 +51 Signed-off-by: Denys Vlasenko --- .../ash_test/ash-arith/arith-ternary_nested1.right | 1 + .../ash_test/ash-arith/arith-ternary_nested1.tests | 2 + shell/ash_test/ash-arith/arith.right | 2 +- shell/hush_test/hush-arith/arith-precedence1.right | 4 ++ shell/hush_test/hush-arith/arith-precedence1.tests | 15 +++++++ shell/hush_test/hush-arith/arith-ternary2.right | 3 -- shell/hush_test/hush-arith/arith-ternary2.tests | 7 ---- .../hush-arith/arith-ternary_nested1.right | 1 + .../hush-arith/arith-ternary_nested1.tests | 2 + shell/hush_test/hush-arith/arith.right | 2 +- shell/math.c | 48 +++++++++++++++------- 11 files changed, 61 insertions(+), 26 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-ternary_nested1.right b/shell/ash_test/ash-arith/arith-ternary_nested1.right new file mode 100644 index 000000000..d80319695 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested1.right @@ -0,0 +1 @@ +3:3 diff --git a/shell/ash_test/ash-arith/arith-ternary_nested1.tests b/shell/ash_test/ash-arith/arith-ternary_nested1.tests new file mode 100755 index 000000000..469584bea --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested1.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 3:$((1?(2?(3):4):5)) diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right index 61fcab55e..8bc78b8d1 100644 --- a/shell/ash_test/ash-arith/arith.right +++ b/shell/ash_test/ash-arith/arith.right @@ -92,7 +92,7 @@ ghi ./arith.tests: line 190: arithmetic syntax error 16 16 ./arith.tests: line 195: arithmetic syntax error -./arith.tests: line 196: malformed ?: operator +./arith.tests: line 196: arithmetic syntax error ./arith.tests: line 197: arithmetic syntax error 9 9 ./arith.tests: line 204: arithmetic syntax error diff --git a/shell/hush_test/hush-arith/arith-precedence1.right b/shell/hush_test/hush-arith/arith-precedence1.right new file mode 100644 index 000000000..3f9320a13 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-precedence1.right @@ -0,0 +1,4 @@ +4:4 +4:4 +4:4 +4:4 diff --git a/shell/hush_test/hush-arith/arith-precedence1.tests b/shell/hush_test/hush-arith/arith-precedence1.tests new file mode 100755 index 000000000..bfef05292 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-precedence1.tests @@ -0,0 +1,15 @@ +exec 2>&1 +# bash documentation says that precedence order is: +# ... +# expr ? expr1 : expr2 +# = *= /= %= += -= <<= >>= &= ^= |= +# exprA , exprB +# but in practice, the rules for expr1 and expr2 are different: +# assignments and commas in expr1 have higher precedence than :?, +# but in expr2 they haven't: +# "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4" +# "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error) +echo 4:$((0 ? 1,2 : 3,4)) +echo 4:$((1 ? 1,2 : 3,4)) +echo 4:"$((0 ? 1,2 : 3,4))" +echo 4:"$((1 ? 1,2 : 3,4))" diff --git a/shell/hush_test/hush-arith/arith-ternary2.right b/shell/hush_test/hush-arith/arith-ternary2.right deleted file mode 100644 index a549b1b5c..000000000 --- a/shell/hush_test/hush-arith/arith-ternary2.right +++ /dev/null @@ -1,3 +0,0 @@ -6:6 -a=b=+err+ -b=6 diff --git a/shell/hush_test/hush-arith/arith-ternary2.tests b/shell/hush_test/hush-arith/arith-ternary2.tests deleted file mode 100755 index cb3163932..000000000 --- a/shell/hush_test/hush-arith/arith-ternary2.tests +++ /dev/null @@ -1,7 +0,0 @@ -exec 2>&1 -a='b=+err+' -b=5 -# The not-taken branch should not parse variables -echo 6:$((0 ? a : ++b)) -echo "a=$a" -echo "b=$b" diff --git a/shell/hush_test/hush-arith/arith-ternary_nested1.right b/shell/hush_test/hush-arith/arith-ternary_nested1.right new file mode 100644 index 000000000..d80319695 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested1.right @@ -0,0 +1 @@ +3:3 diff --git a/shell/hush_test/hush-arith/arith-ternary_nested1.tests b/shell/hush_test/hush-arith/arith-ternary_nested1.tests new file mode 100755 index 000000000..469584bea --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested1.tests @@ -0,0 +1,2 @@ +exec 2>&1 +echo 3:$((1?(2?(3):4):5)) diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right index a8612295e..df8154f97 100644 --- a/shell/hush_test/hush-arith/arith.right +++ b/shell/hush_test/hush-arith/arith.right @@ -94,7 +94,7 @@ ghi hush: arithmetic syntax error 16 16 hush: arithmetic syntax error -hush: malformed ?: operator +hush: arithmetic syntax error hush: arithmetic syntax error 9 9 hush: arithmetic syntax error diff --git a/shell/math.c b/shell/math.c index 748c3b3ad..f6aa02ac2 100644 --- a/shell/math.c +++ b/shell/math.c @@ -157,17 +157,17 @@ typedef unsigned char operator; #define fix_assignment_prec(prec) do { if (prec == 3) prec = 2; } while (0) /* Ternary conditional operator is right associative too */ -// FIXME: -// bash documentation says that precedence order is: -// ... -// expr ? expr1 : expr2 -// = *= /= %= += -= <<= >>= &= ^= |= -// exprA , exprB -// but in practice, the rules for expr1 and expr2 are different: -// assignments and commas in expr1 have higher precedence than ?:, -// but in expr2 they haven't: -// "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4" -// "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error) +/* + * bash documentation says that precedence order is: + * ... + * expr ? expr1 : expr2 + * = *= /= %= += -= <<= >>= &= ^= |= + * exprA , exprB + * What it omits is that expr1 is parsed as if parenthesized + * (this matches the rules of ?: in C language): + * "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4" + * "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error) + */ #define TOK_CONDITIONAL tok_decl(4,0) #define TOK_CONDITIONAL_SEP tok_decl(4,1) @@ -629,6 +629,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* Stack of operator tokens */ operator *const opstack = alloca(expr_len * sizeof(opstack[0])); operator *opstackptr = opstack; + operator insert_op = 0xff; /* Start with a left paren */ dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack)); @@ -751,11 +752,24 @@ evaluate_string(arith_state_t *math_state, const char *expr) goto err; } } + /* NB: expr now points past the operator */ tok_found: op = p[1]; /* fetch TOK_foo value */ - tok_found1: - /* NB: expr now points past the operator */ + /* Special rule for "? EXPR :" + * "EXPR in the middle of ? : is parsed as if parenthesized" + * (this quirk originates in C grammar, I think). + */ + if (op == TOK_CONDITIONAL) { + insert_op = TOK_LPAREN; + dbg("insert_op=%02x", insert_op); + } + if (op == TOK_CONDITIONAL_SEP) { + insert_op = op; + op = TOK_RPAREN; + dbg("insert_op=%02x op=%02x", insert_op, op); + } + tok_found1: /* post grammar: a++ reduce to num */ if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) lasttok = TOK_NUM; @@ -865,9 +879,15 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ /* else: LPAREN or UNARY: push it on opstack */ push_op: /* Push this operator to opstack */ - dbg("(%d) op:%02x", (int)(opstackptr - opstack), op); + dbg("(%d) op:%02x insert_op:%02x", (int)(opstackptr - opstack), op, insert_op); *opstackptr++ = lasttok = op; next: ; + if (insert_op != 0xff) { + op = insert_op; + insert_op = 0xff; + dbg("inserting %02x", op); + goto tok_found1; + } } /* while (1) */ err: From vda.linux at googlemail.com Thu Jun 15 09:55:56 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 11:55:56 +0200 Subject: [git commit] libbb: code shrink: introduce and use [_]exit_FAILURE() Message-ID: <20230615100000.A2BA183947@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=648f506949ded749e28186d0092b6e42085c897b branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta exit_FAILURE - 7 +7 _exit_FAILURE - 7 +7 run 198 199 +1 restore_state_and_exit 114 115 +1 xbsd_write_bootstrap 399 397 -2 vfork_compressor 209 207 -2 sig_handler 12 10 -2 serial_ctl 154 152 -2 parse_args 1169 1167 -2 onintr 21 19 -2 make_new_session 493 491 -2 login_main 988 986 -2 gotsig 35 33 -2 do_iplink 1315 1313 -2 addgroup_main 397 395 -2 inetd_main 1911 1908 -3 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 2/12 up/down: 16/-25) Total: -9 bytes Signed-off-by: Denys Vlasenko --- applets/individual.c | 2 +- archival/tar.c | 2 +- console-tools/resize.c | 2 +- findutils/xargs.c | 2 +- include/libbb.h | 2 ++ init/init.c | 2 +- libbb/xfuncs.c | 10 ++++++++++ loginutils/addgroup.c | 2 +- loginutils/login.c | 10 +++++----- miscutils/devfsd.c | 6 +++--- miscutils/setserial.c | 2 +- networking/inetd.c | 4 ++-- networking/libiproute/iplink.c | 2 +- networking/libiproute/iptunnel.c | 2 +- networking/ping.c | 2 +- networking/slattach.c | 2 +- networking/telnetd.c | 2 +- procps/powertop.c | 2 +- selinux/setfiles.c | 2 +- util-linux/fdisk_osf.c | 5 +++-- util-linux/more.c | 2 +- 21 files changed, 40 insertions(+), 27 deletions(-) diff --git a/applets/individual.c b/applets/individual.c index e94f26c93..2f743d906 100644 --- a/applets/individual.c +++ b/applets/individual.c @@ -20,5 +20,5 @@ int main(int argc, char **argv) void bb_show_usage(void) { fputs_stdout(APPLET_full_usage "\n"); - exit(EXIT_FAILURE); + exit_FAILURE(); } diff --git a/archival/tar.c b/archival/tar.c index 9de37592e..d6ca6c1e0 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -621,7 +621,7 @@ static void NOINLINE vfork_compressor(int tar_fd, const char *gzip) execlp(gzip, gzip, "-f", (char *)0); vfork_exec_errno = errno; - _exit(EXIT_FAILURE); + _exit_FAILURE(); } /* parent */ diff --git a/console-tools/resize.c b/console-tools/resize.c index 056e33750..95e0c7bf9 100644 --- a/console-tools/resize.c +++ b/console-tools/resize.c @@ -45,7 +45,7 @@ static void onintr(int sig UNUSED_PARAM) { tcsetattr(STDERR_FILENO, TCSANOW, old_termios_p); - _exit(EXIT_FAILURE); + _exit_FAILURE(); } int resize_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; diff --git a/findutils/xargs.c b/findutils/xargs.c index 067ef41c5..c6000e066 100644 --- a/findutils/xargs.c +++ b/findutils/xargs.c @@ -800,7 +800,7 @@ void bb_show_usage(void) { fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n", applet_name); - exit(EXIT_FAILURE); + exit_FAILURE(); } int main(int argc, char **argv) diff --git a/include/libbb.h b/include/libbb.h index 6191debb1..18336da23 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1281,6 +1281,8 @@ void set_task_comm(const char *comm) FAST_FUNC; #endif void exit_SUCCESS(void) NORETURN FAST_FUNC; void _exit_SUCCESS(void) NORETURN FAST_FUNC; +void exit_FAILURE(void) NORETURN FAST_FUNC; +void _exit_FAILURE(void) NORETURN FAST_FUNC; /* Helpers for daemonization. * diff --git a/init/init.c b/init/init.c index 1e1ce833d..2ee1e4cde 100644 --- a/init/init.c +++ b/init/init.c @@ -500,7 +500,7 @@ static pid_t run(const struct init_action *a) /* Open the new terminal device */ if (!open_stdio_to_tty(a->terminal)) - _exit(EXIT_FAILURE); + _exit_FAILURE(); /* NB: on NOMMU we can't wait for input in child, so * "askfirst" will work the same as "respawn". */ diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c index 465e5366c..b03af8542 100644 --- a/libbb/xfuncs.c +++ b/libbb/xfuncs.c @@ -433,3 +433,13 @@ void FAST_FUNC _exit_SUCCESS(void) { _exit(EXIT_SUCCESS); } + +void FAST_FUNC exit_FAILURE(void) +{ + exit(EXIT_FAILURE); +} + +void FAST_FUNC _exit_FAILURE(void) +{ + _exit(EXIT_FAILURE); +} diff --git a/loginutils/addgroup.c b/loginutils/addgroup.c index 2a83c8a15..71d3a8db9 100644 --- a/loginutils/addgroup.c +++ b/loginutils/addgroup.c @@ -102,7 +102,7 @@ static void new_group(char *group, gid_t gid) /* add entry to group */ p = xasprintf("x:%u:", (unsigned) gr.gr_gid); if (update_passwd(bb_path_group_file, group, p, NULL) < 0) - exit(EXIT_FAILURE); + exit_FAILURE(); if (ENABLE_FEATURE_CLEAN_UP) free(p); #if ENABLE_FEATURE_SHADOWPASSWDS diff --git a/loginutils/login.c b/loginutils/login.c index 332238181..b02be2176 100644 --- a/loginutils/login.c +++ b/loginutils/login.c @@ -173,7 +173,7 @@ static void die_if_nologin(void) fflush_all(); /* Users say that they do need this prior to exit: */ tcdrain(STDOUT_FILENO); - exit(EXIT_FAILURE); + exit_FAILURE(); } #else # define die_if_nologin() ((void)0) @@ -265,19 +265,19 @@ static void get_username_or_die(char *buf, int size_buf) do { c = getchar(); if (c == EOF) - exit(EXIT_FAILURE); + exit_FAILURE(); if (c == '\n') { if (!--cntdown) - exit(EXIT_FAILURE); + exit_FAILURE(); goto prompt; } } while (isspace(c)); /* maybe isblank? */ *buf++ = c; if (!fgets(buf, size_buf-2, stdin)) - exit(EXIT_FAILURE); + exit_FAILURE(); if (!strchr(buf, '\n')) - exit(EXIT_FAILURE); + exit_FAILURE(); while ((unsigned char)*buf > ' ') buf++; *buf = '\0'; diff --git a/miscutils/devfsd.c b/miscutils/devfsd.c index 297693f8c..36b491595 100644 --- a/miscutils/devfsd.c +++ b/miscutils/devfsd.c @@ -354,10 +354,10 @@ static const char bb_msg_variable_not_found[] ALIGN1 = "variable: %s not found"; #define simple_info_logger(p, msg) #define msg_logger(p, fmt, args...) #define simple_msg_logger(p, msg) -#define msg_logger_and_die(p, fmt, args...) exit(EXIT_FAILURE) -#define simple_msg_logger_and_die(p, msg) exit(EXIT_FAILURE) +#define msg_logger_and_die(p, fmt, args...) exit_FAILURE() +#define simple_msg_logger_and_die(p, msg) exit_FAILURE() #define error_logger(p, fmt, args...) -#define error_logger_and_die(p, fmt, args...) exit(EXIT_FAILURE) +#define error_logger_and_die(p, fmt, args...) exit_FAILURE() #endif static void safe_memcpy(char *dest, const char *src, int len) diff --git a/miscutils/setserial.c b/miscutils/setserial.c index 2006861e2..175c788fc 100644 --- a/miscutils/setserial.c +++ b/miscutils/setserial.c @@ -535,7 +535,7 @@ static int serial_ctl(int fd, int ops, struct serial_struct *serinfo) bb_simple_perror_msg(err); if (ops & CTL_NODIE) goto nodie; - exit(EXIT_FAILURE); + exit_FAILURE(); } static void print_flag(const char **prefix, const char *flag) diff --git a/networking/inetd.c b/networking/inetd.c index fb2fbe323..e63edcd9d 100644 --- a/networking/inetd.c +++ b/networking/inetd.c @@ -1449,7 +1449,7 @@ int inetd_main(int argc UNUSED_PARAM, char **argv) else sep->se_builtin->bi_dgram_fn(ctrl, sep); if (pid) /* we did fork */ - _exit(EXIT_FAILURE); + _exit_FAILURE(); maybe_close(accepted_fd); continue; /* -> check next fd in fd set */ } @@ -1530,7 +1530,7 @@ int inetd_main(int argc UNUSED_PARAM, char **argv) /* eat packet in udp case */ if (sep->se_socktype != SOCK_STREAM) recv(0, line, LINE_SIZE, MSG_DONTWAIT); - _exit(EXIT_FAILURE); + _exit_FAILURE(); } /* for (sep = servtab...) */ } /* for (;;) */ } diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c index 68d199044..9eb0b4f5f 100644 --- a/networking/libiproute/iplink.c +++ b/networking/libiproute/iplink.c @@ -215,7 +215,7 @@ static void parse_address(char *dev, int hatype, int halen, char *lla, struct if alen = hatype == 1/*ARPHRD_ETHER*/ ? 14/*ETH_HLEN*/ : 19/*INFINIBAND_HLEN*/; alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), alen, lla); if (alen < 0) - exit(EXIT_FAILURE); + exit_FAILURE(); if (alen != halen) { bb_error_msg_and_die("wrong address (%s) length: expected %d bytes", lla, halen); } diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c index c9fa632f3..1ec81c635 100644 --- a/networking/libiproute/iptunnel.c +++ b/networking/libiproute/iptunnel.c @@ -319,7 +319,7 @@ static void parse_args(char **argv, int cmd, struct ip_tunnel_parm *p) struct ip_tunnel_parm old_p; memset(&old_p, 0, sizeof(old_p)); if (do_get_ioctl(*argv, &old_p)) - exit(EXIT_FAILURE); + exit_FAILURE(); *p = old_p; } } diff --git a/networking/ping.c b/networking/ping.c index 9805695a1..b7e6955a9 100644 --- a/networking/ping.c +++ b/networking/ping.c @@ -247,7 +247,7 @@ struct globals { static void noresp(int ign UNUSED_PARAM) { printf("No response from %s\n", G.hostname); - exit(EXIT_FAILURE); + exit_FAILURE(); } static void ping4(len_and_sockaddr *lsa) diff --git a/networking/slattach.c b/networking/slattach.c index 6d2a252fc..16b4c9158 100644 --- a/networking/slattach.c +++ b/networking/slattach.c @@ -80,7 +80,7 @@ static void restore_state_and_exit(int exitcode) /* Restore line status */ if (tcsetattr_serial_or_warn(&G.saved_state)) - exit(EXIT_FAILURE); + exit_FAILURE(); if (ENABLE_FEATURE_CLEAN_UP) close(serial_fd); diff --git a/networking/telnetd.c b/networking/telnetd.c index 0805e464f..fb90e7f11 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -571,7 +571,7 @@ make_new_session( BB_EXECVP(G.loginpath, (char **)login_argv); /* _exit is safer with vfork, and we shouldn't send message * to remote clients anyway */ - _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/ + _exit_FAILURE(); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/ } #if ENABLE_FEATURE_TELNETD_STANDALONE diff --git a/procps/powertop.c b/procps/powertop.c index 18ddaa3ec..8d5d9295d 100644 --- a/procps/powertop.c +++ b/procps/powertop.c @@ -109,7 +109,7 @@ static void reset_term(void) static void sig_handler(int signo UNUSED_PARAM) { reset_term(); - _exit(EXIT_FAILURE); + _exit_FAILURE(); } #endif diff --git a/selinux/setfiles.c b/selinux/setfiles.c index a617b95d8..70e68a666 100644 --- a/selinux/setfiles.c +++ b/selinux/setfiles.c @@ -687,7 +687,7 @@ int setfiles_main(int argc UNUSED_PARAM, char **argv) bb_simple_perror_msg_and_die(argv[0]); } if (nerr) - exit(EXIT_FAILURE); + exit_FAILURE(); argv++; } diff --git a/util-linux/fdisk_osf.c b/util-linux/fdisk_osf.c index 6c66c130d..049f0b169 100644 --- a/util-linux/fdisk_osf.c +++ b/util-linux/fdisk_osf.c @@ -746,11 +746,12 @@ xbsd_write_bootstrap(void) return; e = d + sizeof(struct xbsd_disklabel); - for (p = d; p < e; p++) + for (p = d; p < e; p++) { if (*p) { printf("Bootstrap overlaps with disk label!\n"); - exit(EXIT_FAILURE); + exit_FAILURE(); } + } memmove(d, &dl, sizeof(struct xbsd_disklabel)); diff --git a/util-linux/more.c b/util-linux/more.c index a830dcbc1..352a3b6cf 100644 --- a/util-linux/more.c +++ b/util-linux/more.c @@ -65,7 +65,7 @@ static void gotsig(int sig UNUSED_PARAM) * therefore it is safe in signal handler */ bb_putchar_stderr('\n'); tcsetattr_tty_TCSANOW(&G.initial_settings); - _exit(EXIT_FAILURE); + _exit_FAILURE(); } #define CONVERTED_TAB_SIZE 8 From vda.linux at googlemail.com Thu Jun 15 10:44:43 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 12:44:43 +0200 Subject: [git commit] shell: document another arithmetic discrepancy with bash Message-ID: <20230615104541.56F6D83981@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=22cb0d573a5cabd24fa648f1a13c22acf661a4a3 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-assign-in-varexp1.right | 2 ++ shell/ash_test/ash-arith/arith-assign-in-varexp1.tests | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp1.right b/shell/ash_test/ash-arith/arith-assign-in-varexp1.right new file mode 100644 index 000000000..1feb307d2 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-assign-in-varexp1.right @@ -0,0 +1,2 @@ +7:7 +x=3 diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests b/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests new file mode 100755 index 000000000..fc8ac9d97 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests @@ -0,0 +1,9 @@ +exec 2>&1 +a='x=1' +b='x=2' +c='x=3' +# The variables should evaluate immediately when they encountered, +# not when they go into an operation. Here, order of evaluation +# of names to numbers should be a,b,c - not b,c,a: +echo 7:$((a+b*c)) +echo "x=$x" From vda.linux at googlemail.com Thu Jun 15 14:46:31 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 16:46:31 +0200 Subject: [git commit] shell/math: explain the logic, small tweak to make code smaller Message-ID: <20230615150448.864DC8399F@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=38f423cc9c6419f86e8ab602f195a9035a879001 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1258 1257 -1 Signed-off-by: Denys Vlasenko --- shell/math.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/shell/math.c b/shell/math.c index 7e2bf5ea5..4623c979e 100644 --- a/shell/math.c +++ b/shell/math.c @@ -133,7 +133,10 @@ typedef unsigned char operator; #define tok_decl(prec,id) (((id)<<5) | (prec)) #define PREC(op) ((op) & 0x1F) +#define PREC_LPAREN 0 #define TOK_LPAREN tok_decl(0,0) +/* Precedence value of RPAREN is used only to distinguish it from LPAREN */ +#define TOK_RPAREN tok_decl(1,1) #define TOK_COMMA tok_decl(1,0) @@ -223,7 +226,6 @@ typedef unsigned char operator; #define SPEC_PREC (UNARYPREC+4) #define TOK_NUM tok_decl(SPEC_PREC, 0) -#define TOK_RPAREN tok_decl(SPEC_PREC, 1) static int is_assign_op(operator op) @@ -789,19 +791,32 @@ evaluate_string(arith_state_t *math_state, const char *expr) * stack until we find an operator with a lesser priority than the * one we have just extracted. If op is right-associative, * then stop "applying" on the equal priority too. - * Left paren is given the lowest priority so it will never be - * "applied" in this way. + * Left paren will never be "applied" in this way. */ prec = PREC(op); - if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { - /* not left paren or unary */ + if (prec != PREC_LPAREN && prec < UNARYPREC) { + /* binary, ternary or RPAREN */ if (lasttok != TOK_NUM) { - /* binary op must be preceded by a num */ + /* must be preceded by a num */ goto err; } - /* The algorithm employed here is simple: while we don't - * hit an open paren nor the bottom of the stack, pop - * tokens and apply them */ + /* if op is RPAREN: + * while opstack is not empty: + * pop prev_op + * if prev_op is LPAREN (finished evaluating (EXPR)): + * goto N + * evaluate prev_op on top of numstack + * BUG (unpaired RPAREN) + * else (op is not RPAREN): + * while opstack is not empty: + * pop prev_op + * if can't evaluate prev_op (it is lower precedence than op): + * push prev_op back + * goto P + * evaluate prev_op on top of numstack + * P: push op + * N: loop to parse the rest of string + */ while (opstackptr != opstack) { operator prev_op = *--opstackptr; if (op == TOK_RPAREN) { @@ -821,7 +836,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (prev_prec < prec || (prev_prec == prec && is_right_associative(prec)) ) { - /* ...x~y@: push @ on opstack */ + /* ...x~y at . push @ on opstack */ opstackptr++; /* undo removal of ~ op */ goto push_op; } From vda.linux at googlemail.com Thu Jun 15 11:56:12 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 13:56:12 +0200 Subject: [git commit] shell/math: fix order of expansion of variables to numbers Message-ID: <20230615150448.77B3E8399C@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=ea6dcbe2839fb21049baadd0d5da903ae11661ec branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master This fixes arith-assign-in-varexp1.tests function old new delta evaluate_string 1132 1258 +126 arith_lookup_val 143 - -143 arith_apply 1132 977 -155 ------------------------------------------------------------------------------ (add/remove: 0/1 grow/shrink: 1/1 up/down: 126/-298) Total: -172 bytes Signed-off-by: Denys Vlasenko --- .../hush-arith/arith-assign-in-varexp1.right | 2 + .../hush-arith/arith-assign-in-varexp1.tests | 9 ++++ shell/math.c | 49 +++++++--------------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp1.right b/shell/hush_test/hush-arith/arith-assign-in-varexp1.right new file mode 100644 index 000000000..1feb307d2 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-assign-in-varexp1.right @@ -0,0 +1,2 @@ +7:7 +x=3 diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests b/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests new file mode 100755 index 000000000..fc8ac9d97 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests @@ -0,0 +1,9 @@ +exec 2>&1 +a='x=1' +b='x=2' +c='x=3' +# The variables should evaluate immediately when they encountered, +# not when they go into an operation. Here, order of evaluation +# of names to numbers should be a,b,c - not b,c,a: +echo 7:$((a+b*c)) +echo "x=$x" diff --git a/shell/math.c b/shell/math.c index 8d0c9dea7..7e2bf5ea5 100644 --- a/shell/math.c +++ b/shell/math.c @@ -310,7 +310,6 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ var_or_num_t *top_of_stack; arith_t rez; - const char *err; /* There is no operator that can work without arguments */ if (NUMPTR == numstack) @@ -324,14 +323,8 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ NUMPTR = expr1 + 1; if (expr1 < numstack) /* Example: $((2:3)) */ return "malformed ?: operator"; - err = arith_lookup_val(math_state, expr1); - if (err) - return err; if (expr1->val != 0) /* select expr2 or expr3 */ top_of_stack--; - err = arith_lookup_val(math_state, top_of_stack); - if (err) - return err; expr1->val = top_of_stack->val; expr1->var_name = NULL; return NULL; @@ -339,24 +332,6 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (op == TOK_CONDITIONAL) /* Example: $((a ? b)) */ return "malformed ?: operator"; - if (PREC(op) < UNARYPREC) { - /* In binops a ~ b, variables are resolved left-to-right, - * resolve top_of_stack[-1] _before_ resolving top_of_stack[0] - */ - if (top_of_stack == numstack) /* need two arguments */ - goto syntax_err; - /* Unless it is =, resolve top_of_stack[-1] name to value */ - if (op != TOK_ASSIGN) { - err = arith_lookup_val(math_state, top_of_stack - 1); - if (err) - return err; - } - } - /* Resolve top_of_stack[0] name to value */ - err = arith_lookup_val(math_state, top_of_stack); - if (err) - return err; - rez = top_of_stack->val; if (op == TOK_UMINUS) rez = -rez; @@ -372,6 +347,9 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ /* Binary operators */ arith_t right_side_val; + if (top_of_stack == numstack) /* have two arguments? */ + goto syntax_err; /* no */ + /* Pop numstack */ NUMPTR = top_of_stack; /* this decrements NUMPTR */ top_of_stack--; /* now points to left side */ @@ -677,7 +655,16 @@ evaluate_string(arith_state_t *math_state, const char *expr) numstackptr->var_name = alloca(var_name_size); safe_strncpy(numstackptr->var_name, expr, var_name_size); dbg("[%d] var:'%s'", (int)(numstackptr - numstack), numstackptr->var_name); - expr = p; + expr = skip_whitespace(p); + /* If it is not followed by "=" operator... */ + if (expr[0] != '=' /* not "=..." */ + || expr[1] == '=' /* or "==..." */ + ) { + /* Evaluate variable to value */ + errmsg = arith_lookup_val(math_state, numstackptr); + if (errmsg) + goto err_with_custom_msg; + } push_num: numstackptr++; lasttok = TOK_NUM; @@ -819,14 +806,8 @@ evaluate_string(arith_state_t *math_state, const char *expr) operator prev_op = *--opstackptr; if (op == TOK_RPAREN) { if (prev_op == TOK_LPAREN) { - if (VALID_NAME(numstackptr[-1].var_name)) { - /* Expression is (var), lookup now */ - errmsg = arith_lookup_val(math_state, &numstackptr[-1]); - if (errmsg) - goto err_with_custom_msg; - /* Erase var name: (var) is just a number, for example, (var) = 1 is not valid */ - numstackptr[-1].var_name = NULL; - } + /* Erase var name: (var) is just a number, for example, (var) = 1 is not valid */ + numstackptr[-1].var_name = NULL; /* Any operator directly after a * close paren should consider itself binary */ lasttok = TOK_NUM; From vda.linux at googlemail.com Thu Jun 15 15:16:46 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 15 Jun 2023 17:16:46 +0200 Subject: [git commit] shell/math: simplify handling of unary plus Message-ID: <20230615151856.51300839A4@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=f8263528cd44ac5dc95778556c6fd3feea14742e branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1257 1271 +14 arith_apply 977 968 -9 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 14/-9) Total: 5 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shell/math.c b/shell/math.c index 4623c979e..a398bcb98 100644 --- a/shell/math.c +++ b/shell/math.c @@ -345,7 +345,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ rez++; else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) rez--; - else if (op != TOK_UPLUS) { + else /*if (op != TOK_UPLUS) - always true, we drop TOK_UPLUS earlier */ { /* Binary operators */ arith_t right_side_val; @@ -770,8 +770,10 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (lasttok != TOK_NUM) { switch (op) { case TOK_ADD: - op = TOK_UPLUS; - break; + //op = TOK_UPLUS; + //break; + /* Unary plus does nothing, do not even push it to opstack */ + continue; case TOK_SUB: op = TOK_UMINUS; break; From vda.linux at googlemail.com Fri Jun 16 17:51:01 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Fri, 16 Jun 2023 19:51:01 +0200 Subject: [git commit] shell/math: fix ?: to not evaluate not-taken branches Message-ID: <20230616175412.613D683B00@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=e1279858394a6079be6816cbedaa3f10e74057cc branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master This fixes ash-arith-arith-ternary1/2.tests function old new delta evaluate_string 1271 1432 +161 arith_apply 968 1000 +32 arith 22 36 +14 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 207/0) Total: 207 bytes Signed-off-by: Denys Vlasenko --- .../ash_test/ash-arith/arith-ternary-assign.right | 1 + .../ash_test/ash-arith/arith-ternary-assign.tests | 3 + shell/ash_test/ash-arith/arith-ternary-comma.right | 1 + shell/ash_test/ash-arith/arith-ternary-comma.tests | 3 + .../ash_test/ash-arith/arith-ternary-preincr.right | 1 + .../ash_test/ash-arith/arith-ternary-preincr.tests | 3 + shell/ash_test/ash-arith/arith-ternary3.right | 1 + shell/ash_test/ash-arith/arith-ternary3.tests | 4 + .../ash_test/ash-arith/arith-ternary_nested3.right | 2 + .../ash_test/ash-arith/arith-ternary_nested3.tests | 6 ++ .../hush-arith/arith-ternary-assign.right | 1 + .../hush-arith/arith-ternary-assign.tests | 3 + .../hush_test/hush-arith/arith-ternary-comma.right | 1 + .../hush_test/hush-arith/arith-ternary-comma.tests | 3 + .../hush-arith/arith-ternary-preincr.right | 1 + .../hush-arith/arith-ternary-preincr.tests | 3 + shell/hush_test/hush-arith/arith-ternary1.right | 2 + shell/hush_test/hush-arith/arith-ternary1.tests | 5 ++ shell/hush_test/hush-arith/arith-ternary2.right | 3 + shell/hush_test/hush-arith/arith-ternary2.tests | 7 ++ shell/hush_test/hush-arith/arith-ternary3.right | 1 + shell/hush_test/hush-arith/arith-ternary3.tests | 4 + .../hush-arith/arith-ternary_nested3.right | 2 + .../hush-arith/arith-ternary_nested3.tests | 6 ++ shell/math.c | 85 ++++++++++++++++------ shell/math.h | 3 +- 26 files changed, 131 insertions(+), 24 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-ternary-assign.right b/shell/ash_test/ash-arith/arith-ternary-assign.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-assign.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/ash_test/ash-arith/arith-ternary-assign.tests b/shell/ash_test/ash-arith/arith-ternary-assign.tests new file mode 100755 index 000000000..fa18fe7b9 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-assign.tests @@ -0,0 +1,3 @@ +exec 2>&1 +a='@' +echo 42:$((a=1?42:3,a)) diff --git a/shell/ash_test/ash-arith/arith-ternary-comma.right b/shell/ash_test/ash-arith/arith-ternary-comma.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-comma.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/ash_test/ash-arith/arith-ternary-comma.tests b/shell/ash_test/ash-arith/arith-ternary-comma.tests new file mode 100755 index 000000000..5e05b58c4 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-comma.tests @@ -0,0 +1,3 @@ +exec 2>&1 +x='@' +echo 42:$((1?4:x,20*2+2)) diff --git a/shell/ash_test/ash-arith/arith-ternary-preincr.right b/shell/ash_test/ash-arith/arith-ternary-preincr.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-preincr.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/ash_test/ash-arith/arith-ternary-preincr.tests b/shell/ash_test/ash-arith/arith-ternary-preincr.tests new file mode 100755 index 000000000..3985c7079 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary-preincr.tests @@ -0,0 +1,3 @@ +exec 2>&1 +x='@' +echo 42:$((1?42:++x)) diff --git a/shell/ash_test/ash-arith/arith-ternary3.right b/shell/ash_test/ash-arith/arith-ternary3.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary3.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/ash_test/ash-arith/arith-ternary3.tests b/shell/ash_test/ash-arith/arith-ternary3.tests new file mode 100755 index 000000000..0bf9f3002 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary3.tests @@ -0,0 +1,4 @@ +exec 2>&1 +# "EXPR ?..." should check _evaluated_ EXPR, +# not its last value +echo 42:$((1 < 1 ? -1 : 1 > 1 ? 1 : 42)) diff --git a/shell/ash_test/ash-arith/arith-ternary_nested3.right b/shell/ash_test/ash-arith/arith-ternary_nested3.right new file mode 100644 index 000000000..1a34fde65 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested3.right @@ -0,0 +1,2 @@ +42:42 +a=2:2 diff --git a/shell/ash_test/ash-arith/arith-ternary_nested3.tests b/shell/ash_test/ash-arith/arith-ternary_nested3.tests new file mode 100755 index 000000000..b69dcc6e9 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-ternary_nested3.tests @@ -0,0 +1,6 @@ +exec 2>&1 +x='@' +a=2 +# After processing nested ?:, outermost ?: should still rememeber to NOT evaluate a*=2 +echo 42:$((1?0?41:42:(a*=2))) +echo "a=2:$a" diff --git a/shell/hush_test/hush-arith/arith-ternary-assign.right b/shell/hush_test/hush-arith/arith-ternary-assign.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-assign.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/hush_test/hush-arith/arith-ternary-assign.tests b/shell/hush_test/hush-arith/arith-ternary-assign.tests new file mode 100755 index 000000000..fa18fe7b9 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-assign.tests @@ -0,0 +1,3 @@ +exec 2>&1 +a='@' +echo 42:$((a=1?42:3,a)) diff --git a/shell/hush_test/hush-arith/arith-ternary-comma.right b/shell/hush_test/hush-arith/arith-ternary-comma.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-comma.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/hush_test/hush-arith/arith-ternary-comma.tests b/shell/hush_test/hush-arith/arith-ternary-comma.tests new file mode 100755 index 000000000..5e05b58c4 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-comma.tests @@ -0,0 +1,3 @@ +exec 2>&1 +x='@' +echo 42:$((1?4:x,20*2+2)) diff --git a/shell/hush_test/hush-arith/arith-ternary-preincr.right b/shell/hush_test/hush-arith/arith-ternary-preincr.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-preincr.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/hush_test/hush-arith/arith-ternary-preincr.tests b/shell/hush_test/hush-arith/arith-ternary-preincr.tests new file mode 100755 index 000000000..3985c7079 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary-preincr.tests @@ -0,0 +1,3 @@ +exec 2>&1 +x='@' +echo 42:$((1?42:++x)) diff --git a/shell/hush_test/hush-arith/arith-ternary1.right b/shell/hush_test/hush-arith/arith-ternary1.right new file mode 100644 index 000000000..6b751d7b8 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary1.right @@ -0,0 +1,2 @@ +42:42 +a=0 diff --git a/shell/hush_test/hush-arith/arith-ternary1.tests b/shell/hush_test/hush-arith/arith-ternary1.tests new file mode 100755 index 000000000..3532ce54d --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary1.tests @@ -0,0 +1,5 @@ +exec 2>&1 +a=0 +# The not-taken branch should not evaluate +echo 42:$((1 ? 42 : (a+=2))) +echo "a=$a" diff --git a/shell/hush_test/hush-arith/arith-ternary2.right b/shell/hush_test/hush-arith/arith-ternary2.right new file mode 100644 index 000000000..a549b1b5c --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary2.right @@ -0,0 +1,3 @@ +6:6 +a=b=+err+ +b=6 diff --git a/shell/hush_test/hush-arith/arith-ternary2.tests b/shell/hush_test/hush-arith/arith-ternary2.tests new file mode 100755 index 000000000..cb3163932 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary2.tests @@ -0,0 +1,7 @@ +exec 2>&1 +a='b=+err+' +b=5 +# The not-taken branch should not parse variables +echo 6:$((0 ? a : ++b)) +echo "a=$a" +echo "b=$b" diff --git a/shell/hush_test/hush-arith/arith-ternary3.right b/shell/hush_test/hush-arith/arith-ternary3.right new file mode 100644 index 000000000..6644d86bf --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary3.right @@ -0,0 +1 @@ +42:42 diff --git a/shell/hush_test/hush-arith/arith-ternary3.tests b/shell/hush_test/hush-arith/arith-ternary3.tests new file mode 100755 index 000000000..0bf9f3002 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary3.tests @@ -0,0 +1,4 @@ +exec 2>&1 +# "EXPR ?..." should check _evaluated_ EXPR, +# not its last value +echo 42:$((1 < 1 ? -1 : 1 > 1 ? 1 : 42)) diff --git a/shell/hush_test/hush-arith/arith-ternary_nested3.right b/shell/hush_test/hush-arith/arith-ternary_nested3.right new file mode 100644 index 000000000..1a34fde65 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested3.right @@ -0,0 +1,2 @@ +42:42 +a=2:2 diff --git a/shell/hush_test/hush-arith/arith-ternary_nested3.tests b/shell/hush_test/hush-arith/arith-ternary_nested3.tests new file mode 100755 index 000000000..b69dcc6e9 --- /dev/null +++ b/shell/hush_test/hush-arith/arith-ternary_nested3.tests @@ -0,0 +1,6 @@ +exec 2>&1 +x='@' +a=2 +# After processing nested ?:, outermost ?: should still rememeber to NOT evaluate a*=2 +echo 42:$((1?0?41:42:(a*=2))) +echo "a=2:$a" diff --git a/shell/math.c b/shell/math.c index a398bcb98..b1aabef9d 100644 --- a/shell/math.c +++ b/shell/math.c @@ -356,6 +356,11 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ NUMPTR = top_of_stack; /* this decrements NUMPTR */ top_of_stack--; /* now points to left side */ + if (math_state->evaluation_disabled) { + dbg("binary op %02x skipped", op); + goto ret_NULL; + } + right_side_val = rez; rez = top_of_stack->val; if (op == TOK_BOR || op == TOK_OR_ASSIGN) @@ -428,6 +433,11 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ } } + if (math_state->evaluation_disabled) { + dbg("unary op %02x skipped", op); + goto ret_NULL; + } + if (is_assign_op(op)) { char buf[sizeof(arith_t)*3 + 2]; @@ -446,6 +456,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ } top_of_stack->val = rez; + ret_NULL: /* Erase var name, it is just a number now */ top_of_stack->var_name = NULL; return NULL; @@ -594,8 +605,10 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) static arith_t evaluate_string(arith_state_t *math_state, const char *expr) { +#define EVAL_DISABLED ((unsigned long long)math_state->evaluation_disabled) +#define TOP_BIT_ULL ((unsigned long long)LLONG_MAX + 1) operator lasttok; - const char *errmsg; + const char *errmsg = NULL; const char *start_expr = expr = skip_whitespace(expr); unsigned expr_len = strlen(expr) + 2; /* Stack of integers/names */ @@ -614,7 +627,6 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* Start with a left paren */ dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack)); *opstackptr++ = lasttok = TOK_LPAREN; - errmsg = NULL; while (1) { const char *p; @@ -653,19 +665,26 @@ evaluate_string(arith_state_t *math_state, const char *expr) p = endofname(expr); if (p != expr) { /* Name */ - size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ - numstackptr->var_name = alloca(var_name_size); - safe_strncpy(numstackptr->var_name, expr, var_name_size); - dbg("[%d] var:'%s'", (int)(numstackptr - numstack), numstackptr->var_name); - expr = skip_whitespace(p); - /* If it is not followed by "=" operator... */ - if (expr[0] != '=' /* not "=..." */ - || expr[1] == '=' /* or "==..." */ - ) { - /* Evaluate variable to value */ - errmsg = arith_lookup_val(math_state, numstackptr); - if (errmsg) - goto err_with_custom_msg; + if (!math_state->evaluation_disabled) { + size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ + numstackptr->var_name = alloca(var_name_size); + safe_strncpy(numstackptr->var_name, expr, var_name_size); + dbg("[%d] var:'%s'", (int)(numstackptr - numstack), numstackptr->var_name); + expr = skip_whitespace(p); + /* If it is not followed by "=" operator... */ + if (expr[0] != '=' /* not "=..." */ + || expr[1] == '=' /* or "==..." */ + ) { + /* Evaluate variable to value */ + errmsg = arith_lookup_val(math_state, numstackptr); + if (errmsg) + goto err_with_custom_msg; + } + } else { + dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); + numstackptr->var_name = NULL; + numstackptr->val = 0; //paranoia, probably not needed + expr = p; } push_num: numstackptr++; @@ -814,10 +833,11 @@ evaluate_string(arith_state_t *math_state, const char *expr) * pop prev_op * if can't evaluate prev_op (it is lower precedence than op): * push prev_op back - * goto P + * goto C * evaluate prev_op on top of numstack - * P: push op - * N: loop to parse the rest of string + * C:if op is "?": check result, set disable flag if needed + * push op + * N:loop to parse the rest of string */ while (opstackptr != opstack) { operator prev_op = *--opstackptr; @@ -840,7 +860,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) ) { /* ...x~y at . push @ on opstack */ opstackptr++; /* undo removal of ~ op */ - goto push_op; + goto check_cond; } /* else: ...x~y at . Evaluate x~y, replace it on stack with result. Then repeat */ } @@ -863,21 +883,41 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ errmsg = "malformed ?: operator"; goto err_with_custom_msg; } + /* Example: a=1?2:3,a. We just executed ":". + * Prevent assignment from being still disabled. + */ + math_state->evaluation_disabled >>= 1; + dbg("':' executed: evaluation_disabled=%llx (restored)", EVAL_DISABLED); } } /* while (opstack not empty) */ if (op == TOK_RPAREN) /* unpaired RPAREN? */ goto err; - } + check_cond: + if (op == TOK_CONDITIONAL) { + /* We know the value of EXPR in "EXPR ? ..." + * Should we stop evaluating now? */ + if (math_state->evaluation_disabled & TOP_BIT_ULL) + goto err; /* >63 levels of ?: nesting not supported */ + math_state->evaluation_disabled <<= 1; + if (numstackptr[-1].val == 0) + math_state->evaluation_disabled |= 1; + dbg("'?' entered: evaluation_disabled=%llx", EVAL_DISABLED); + } + } /* if */ /* else: LPAREN or UNARY: push it on opstack */ - push_op: + /* Push this operator to opstack */ dbg("(%d) op:%02x insert_op:%02x", (int)(opstackptr - opstack), op, insert_op); *opstackptr++ = lasttok = op; - next: ; + next: if (insert_op != 0xff) { op = insert_op; insert_op = 0xff; dbg("inserting %02x", op); + if (op == TOK_CONDITIONAL_SEP) { + math_state->evaluation_disabled ^= 1; + dbg("':' entered: evaluation_disabled=%llx (negated)", EVAL_DISABLED); + } goto tok_found1; } } /* while (1) */ @@ -896,6 +936,7 @@ arith(arith_state_t *math_state, const char *expr) { math_state->errmsg = NULL; math_state->list_of_recursed_names = NULL; + math_state->evaluation_disabled = 0; return evaluate_string(math_state, expr); } diff --git a/shell/math.h b/shell/math.h index 41ef6e8df..452ddaddd 100644 --- a/shell/math.h +++ b/shell/math.h @@ -73,13 +73,12 @@ typedef long arith_t; typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val); -//typedef const char* FAST_FUNC (*arith_var_endofname_t)(const char *name); typedef struct arith_state_t { const char *errmsg; arith_var_lookup_t lookupvar; arith_var_set_t setvar; -// arith_var_endofname_t endofname; + uint64_t evaluation_disabled; void *list_of_recursed_names; } arith_state_t; From vda.linux at googlemail.com Fri Jun 16 18:47:43 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Fri, 16 Jun 2023 20:47:43 +0200 Subject: [git commit] shell/math: tweka comments Message-ID: <20230616184815.1B62C83B05@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=550696d492c7389927d3f335bed11aa4decbcae6 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/shell/math.c b/shell/math.c index b1aabef9d..2959e57ea 100644 --- a/shell/math.c +++ b/shell/math.c @@ -46,7 +46,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - /* This is my infix parser/evaluator. It is optimized for size, intended * as a replacement for yacc-based parsers. However, it may well be faster * than a comparable parser written in yacc. The supported operators are @@ -61,7 +60,6 @@ * to the stack instead of adding them to a queue to end up with an * expression). */ - /* * Aug 24, 2001 Manuel Novoa III * @@ -245,7 +243,6 @@ is_right_associative(operator prec) || prec == PREC(TOK_CONDITIONAL); } - typedef struct { arith_t val; char *var_name; @@ -254,13 +251,11 @@ typedef struct { #define VALID_NAME(name) (name) #define NOT_NAME(name) (!(name)) - typedef struct remembered_name { struct remembered_name *next; const char *var_name; } remembered_name; - static arith_t evaluate_string(arith_state_t *math_state, const char *expr); @@ -278,7 +273,7 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) */ for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) { if (strcmp(cur->var_name, t->var_name) == 0) { - /* Yes */ + /* yes */ return "expression recursion loop detected"; } } @@ -500,7 +495,6 @@ static const char op_tokens[] ALIGN1 = { '+', 0, TOK_ADD, '-', 0, TOK_SUB, '^', 0, TOK_BXOR, - /* uniq */ '~', 0, TOK_BNOT, ',', 0, TOK_COMMA, '?', 0, TOK_CONDITIONAL, @@ -869,14 +863,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (errmsg) goto err_with_custom_msg; dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[-1].val, numstackptr[-1].var_name); - /* For ternary ?: we need to remove ? from opstack too, not just : */ if (prev_op == TOK_CONDITIONAL_SEP) { - // This is caught in arith_apply() - //if (opstackptr == opstack) { - // /* Example: $((2:3)) */ - // errmsg = "where is your ? in ?:"; - // goto err_with_custom_msg; - //} + /* We just executed ":" */ + /* Remove "?" from opstack too, not just ":" */ opstackptr--; if (*opstackptr != TOK_CONDITIONAL) { /* Example: $((1,2:3)) */ @@ -890,12 +879,14 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ dbg("':' executed: evaluation_disabled=%llx (restored)", EVAL_DISABLED); } } /* while (opstack not empty) */ + if (op == TOK_RPAREN) /* unpaired RPAREN? */ goto err; check_cond: if (op == TOK_CONDITIONAL) { - /* We know the value of EXPR in "EXPR ? ..." - * Should we stop evaluating now? */ + /* We just now evaluated EXPR before "?". + * Should we disable evaluation now? + */ if (math_state->evaluation_disabled & TOP_BIT_ULL) goto err; /* >63 levels of ?: nesting not supported */ math_state->evaluation_disabled <<= 1; @@ -915,6 +906,7 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ insert_op = 0xff; dbg("inserting %02x", op); if (op == TOK_CONDITIONAL_SEP) { + /* The next token is ":". Toggle "do not evaluate" bit */ math_state->evaluation_disabled ^= 1; dbg("':' entered: evaluation_disabled=%llx (negated)", EVAL_DISABLED); } From bugzilla at busybox.net Fri Jun 16 21:00:19 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Fri, 16 Jun 2023 21:00:19 +0000 Subject: [Bug 15619] Odd beavor of loops with async sleep in 1.136 In-Reply-To: References: Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15619 --- Comment #1 from Tero Saarni --- This is because of built-in sleep [1] introduced in BusyBox 1.36.0 which exits when receiving SIGCHLD. Here is another way how to reproduce: /bin/sleep 1 & sleep 10 The purpose of the first command it to trigger SIGCHLD after one second. The second command should sleep for 10 seconds. - If running it in busybox, the command sequence exits after 1 second - If running it in bash, the command sequence exits after 10 second Similar problem was previously reported for "read" [2] and fix [3] was applied for it. -- Tero [1] https://git.busybox.net/busybox/commit/?id=58598eb7093561d914a6254697e137b815f1fdfc [2] https://www.mail-archive.com/busybox at busybox.net/msg24147.html [3] https://git.busybox.net/busybox/commit/?id=f5470419404d643070db99d058405b714695b817 -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Fri Jun 16 22:17:53 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Fri, 16 Jun 2023 22:17:53 +0000 Subject: [Bug 15619] Odd beavor of loops with async sleep in 1.136 In-Reply-To: References: Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15619 --- Comment #2 from Tero Saarni --- Having a second look, I think it was this fix [1] that causes the problem as a side effect. It was released in 1.36.1. [1] https://git.busybox.net/busybox/commit/?h=1_36_1&id=ce839dea92ce10627094096835e831bf5d267631 -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Fri Jun 16 22:28:49 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 00:28:49 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230616222927.4FE1683B08@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=d6f98f214b3bd242f7404b68a4f9d777114fffa3 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1432 1414 -18 Signed-off-by: Denys Vlasenko --- shell/math.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/math.c b/shell/math.c index 2959e57ea..56f866bf2 100644 --- a/shell/math.c +++ b/shell/math.c @@ -889,9 +889,9 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ */ if (math_state->evaluation_disabled & TOP_BIT_ULL) goto err; /* >63 levels of ?: nesting not supported */ - math_state->evaluation_disabled <<= 1; - if (numstackptr[-1].val == 0) - math_state->evaluation_disabled |= 1; + math_state->evaluation_disabled = + (math_state->evaluation_disabled << 1) + | (numstackptr[-1].val == 0); dbg("'?' entered: evaluation_disabled=%llx", EVAL_DISABLED); } } /* if */ From vda.linux at googlemail.com Sat Jun 17 08:40:29 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 10:40:29 +0200 Subject: [git commit] shell/math.h: update comments, rearrange struct members for smaller code Message-ID: <20230617084143.7DFDA82803@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=6221832bc15d9037360e3bdc9405df08ed801cc1 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1000 998 -2 evaluate_string 1414 1406 -8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-10) Total: -10 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 2 +- shell/math.h | 26 ++++---------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/shell/math.c b/shell/math.c index 56f866bf2..6196a6ad9 100644 --- a/shell/math.c +++ b/shell/math.c @@ -926,9 +926,9 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ arith_t FAST_FUNC arith(arith_state_t *math_state, const char *expr) { + math_state->evaluation_disabled = 0; math_state->errmsg = NULL; math_state->list_of_recursed_names = NULL; - math_state->evaluation_disabled = 0; return evaluate_string(math_state, expr); } diff --git a/shell/math.h b/shell/math.h index 452ddaddd..9812184f1 100644 --- a/shell/math.h +++ b/shell/math.h @@ -6,7 +6,6 @@ * * See math.c for internal documentation. */ - /* The math library has just one function: * * arith_t arith(arith_state_t *state, const char *expr); @@ -22,7 +21,6 @@ * "1 + 2 + 3" * you would obviously get back 6. */ - /* To add support to a shell, you need to implement three functions: * * lookupvar() - look up and return the value of a variable @@ -36,28 +34,12 @@ * setvar() - set a variable to some value * * If the arithmetic expansion does something like: - * $(( i = 1)) + * $((i = 1)) * then the math code will make a call like so: - * setvar("i", "1", 0); + * setvar("i", "1"); * The storage for the first two parameters are not allocated, so your * shell implementation will most likely need to strdup() them to save. - * - * endofname() - return the end of a variable name from input - * - * The arithmetic code does not know about variable naming conventions. - * So when it is given an experession, it knows something is not numeric, - * but it is up to the shell to dictate what is a valid identifiers. - * So when it encounters something like: - * $(( some_var + 123 )) - * It will make a call like so: - * end = endofname("some_var + 123"); - * So the shell needs to scan the input string and return a pointer to the - * first non-identifier string. In this case, it should return the input - * pointer with an offset pointing to the first space. The typical - * implementation will return the offset of first char that does not match - * the regex (in C locale): ^[a-zA-Z_][a-zA-Z_0-9]* */ - #ifndef SHELL_MATH_H #define SHELL_MATH_H 1 @@ -75,11 +57,11 @@ typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val); typedef struct arith_state_t { + uint64_t evaluation_disabled; const char *errmsg; + void *list_of_recursed_names; arith_var_lookup_t lookupvar; arith_var_set_t setvar; - uint64_t evaluation_disabled; - void *list_of_recursed_names; } arith_state_t; arith_t FAST_FUNC arith(arith_state_t *state, const char *expr); From vda.linux at googlemail.com Sat Jun 17 09:03:02 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 11:03:02 +0200 Subject: [git commit] shell/math: change ?: nesting code to not have 63 level nesting limitation Message-ID: <20230617090507.0F21982A58@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=19a74a54ded98b28c672d38b79ea9f313f2d89db branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1406 1432 +26 arith 36 29 -7 arith_apply 998 990 -8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/2 up/down: 26/-15) Total: 11 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 33 ++++++++++++++++++++------------- shell/math.h | 2 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/shell/math.c b/shell/math.c index 6196a6ad9..3e339a5ec 100644 --- a/shell/math.c +++ b/shell/math.c @@ -599,8 +599,6 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) static arith_t evaluate_string(arith_state_t *math_state, const char *expr) { -#define EVAL_DISABLED ((unsigned long long)math_state->evaluation_disabled) -#define TOP_BIT_ULL ((unsigned long long)LLONG_MAX + 1) operator lasttok; const char *errmsg = NULL; const char *start_expr = expr = skip_whitespace(expr); @@ -617,6 +615,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) operator *const opstack = alloca(expr_len * sizeof(opstack[0])); operator *opstackptr = opstack; operator insert_op = 0xff; + unsigned ternary_level = 0; /* Start with a left paren */ dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack)); @@ -875,8 +874,11 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ /* Example: a=1?2:3,a. We just executed ":". * Prevent assignment from being still disabled. */ - math_state->evaluation_disabled >>= 1; - dbg("':' executed: evaluation_disabled=%llx (restored)", EVAL_DISABLED); + if (ternary_level == math_state->evaluation_disabled) { + math_state->evaluation_disabled = 0; + dbg("':' executed: evaluation_disabled=CLEAR"); + } + ternary_level--; } } /* while (opstack not empty) */ @@ -887,12 +889,11 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ /* We just now evaluated EXPR before "?". * Should we disable evaluation now? */ - if (math_state->evaluation_disabled & TOP_BIT_ULL) - goto err; /* >63 levels of ?: nesting not supported */ - math_state->evaluation_disabled = - (math_state->evaluation_disabled << 1) - | (numstackptr[-1].val == 0); - dbg("'?' entered: evaluation_disabled=%llx", EVAL_DISABLED); + ternary_level++; + if (numstackptr[-1].val == 0 && !math_state->evaluation_disabled) { + math_state->evaluation_disabled = ternary_level; + dbg("'?' entered: evaluation_disabled=%u", math_state->evaluation_disabled); + } } } /* if */ /* else: LPAREN or UNARY: push it on opstack */ @@ -906,9 +907,15 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ insert_op = 0xff; dbg("inserting %02x", op); if (op == TOK_CONDITIONAL_SEP) { - /* The next token is ":". Toggle "do not evaluate" bit */ - math_state->evaluation_disabled ^= 1; - dbg("':' entered: evaluation_disabled=%llx (negated)", EVAL_DISABLED); + /* The next token is ":". Toggle "do not evaluate" state */ + if (!math_state->evaluation_disabled) { + math_state->evaluation_disabled = ternary_level; + dbg("':' entered: evaluation_disabled=%u", math_state->evaluation_disabled); + } else if (ternary_level == math_state->evaluation_disabled) { + math_state->evaluation_disabled = 0; + dbg("':' entered: evaluation_disabled=CLEAR"); + } /* else: ternary_level > nonzero evaluation_disabled: we are in nested ?:, in its disabled branch */ + /* do nothing */ } goto tok_found1; } diff --git a/shell/math.h b/shell/math.h index 9812184f1..439031828 100644 --- a/shell/math.h +++ b/shell/math.h @@ -57,7 +57,7 @@ typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val); typedef struct arith_state_t { - uint64_t evaluation_disabled; + unsigned evaluation_disabled; const char *errmsg; void *list_of_recursed_names; arith_var_lookup_t lookupvar; From vda.linux at googlemail.com Sat Jun 17 17:21:28 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 19:21:28 +0200 Subject: [git commit] shell/math: eliminate some redundant stores on return code path Message-ID: <20230617172250.85C4B839E5@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=822590f5e29c613dd1401ba4309c0684426fc4f4 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1432 1412 -20 Signed-off-by: Denys Vlasenko --- shell/math.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/shell/math.c b/shell/math.c index 3e339a5ec..4b56d1397 100644 --- a/shell/math.c +++ b/shell/math.c @@ -303,21 +303,21 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) static NOINLINE const char* arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_or_num_t **numstackptr) { -#define NUMPTR (*numstackptr) +#define NUMSTACKPTR (*numstackptr) var_or_num_t *top_of_stack; arith_t rez; /* There is no operator that can work without arguments */ - if (NUMPTR == numstack) + if (NUMSTACKPTR == numstack) goto syntax_err; - top_of_stack = NUMPTR - 1; + top_of_stack = NUMSTACKPTR - 1; if (op == TOK_CONDITIONAL_SEP) { /* "expr1 ? expr2 : expr3" operation */ var_or_num_t *expr1 = &top_of_stack[-2]; - NUMPTR = expr1 + 1; + NUMSTACKPTR = expr1 + 1; if (expr1 < numstack) /* Example: $((2:3)) */ return "malformed ?: operator"; if (expr1->val != 0) /* select expr2 or expr3 */ @@ -348,12 +348,17 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ goto syntax_err; /* no */ /* Pop numstack */ - NUMPTR = top_of_stack; /* this decrements NUMPTR */ + NUMSTACKPTR = top_of_stack; /* this decrements NUMSTACKPTR */ top_of_stack--; /* now points to left side */ if (math_state->evaluation_disabled) { dbg("binary op %02x skipped", op); goto ret_NULL; + /* bash 5.2.12 does not execute "2/0" in disabled + * branches of ?: (and thus does not complain), + * but complains about negative exp: "2**-1". + * I don't think we need to emulate that. + */ } right_side_val = rez; @@ -457,7 +462,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ return NULL; syntax_err: return "arithmetic syntax error"; -#undef NUMPTR +#undef NUMSTACKPTR } /* longest must be first */ @@ -630,8 +635,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (*expr == '\0') { if (expr == start_expr) { /* Null expression */ - numstack->val = 0; - goto ret; + return 0; } /* This is only reached after all tokens have been extracted from the @@ -650,9 +654,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* At this point, we're done with the expression */ if (numstackptr != numstack + 1) { /* if there is not exactly one result, it's bad */ - goto err; + goto syntax_err; } - goto ret; + return numstack->val; } p = endofname(expr); @@ -697,7 +701,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) * a new number or name. Example: 09v09v09v09v09v09v09v09v09v */ if (isalnum(*expr) || *expr == '_') - goto err; + goto syntax_err; if (errno) numstackptr->val = 0; /* bash compat */ goto push_num; @@ -750,7 +754,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (*p == '\0') { /* No next element, operator not found */ //math_state->syntax_error_at = expr; - goto err; + goto syntax_err; } } /* NB: expr now points past the operator */ @@ -812,7 +816,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* binary, ternary or RPAREN */ if (lasttok != TOK_NUM) { /* must be preceded by a num */ - goto err; + goto syntax_err; } /* if op is RPAREN: * while opstack is not empty: @@ -883,7 +887,7 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ } /* while (opstack not empty) */ if (op == TOK_RPAREN) /* unpaired RPAREN? */ - goto err; + goto syntax_err; check_cond: if (op == TOK_CONDITIONAL) { /* We just now evaluated EXPR before "?". @@ -914,20 +918,19 @@ dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[ } else if (ternary_level == math_state->evaluation_disabled) { math_state->evaluation_disabled = 0; dbg("':' entered: evaluation_disabled=CLEAR"); - } /* else: ternary_level > nonzero evaluation_disabled: we are in nested ?:, in its disabled branch */ - /* do nothing */ + } /* else: ternary_level > evaluation_disabled && evaluation_disabled != 0 */ + /* We are in nested "?:" while in outer "?:" disabled branch */ + /* do_nothing */ } goto tok_found1; } } /* while (1) */ - err: + syntax_err: errmsg = "arithmetic syntax error"; err_with_custom_msg: - numstack->val = -1; - ret: math_state->errmsg = errmsg; - return numstack->val; + return -1; } arith_t FAST_FUNC From vda.linux at googlemail.com Sat Jun 17 17:52:20 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 19:52:20 +0200 Subject: [git commit] shell/read: do not allow empty variable name Message-ID: <20230617175249.97F4683AED@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=2ca9c45953cdb5a1bd6144c6eed5a8f14c551122 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/shell_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/shell_common.c b/shell/shell_common.c index 13163acdf..1eca101b9 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -57,7 +57,7 @@ shell_builtin_read(struct builtin_read_params *params) argv = params->argv; pp = argv; while (*pp) { - if (endofname(*pp)[0] != '\0') { + if (!*pp[0] || endofname(*pp)[0] != '\0') { /* Mimic bash message */ bb_error_msg("read: '%s': bad variable name", *pp); return (const char *)(uintptr_t)1; From bugzilla at busybox.net Sat Jun 17 21:04:58 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Sat, 17 Jun 2023 21:04:58 +0000 Subject: [Bug 15631] New: sleep command after '&' in a shell script has no effect Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15631 Bug ID: 15631 Summary: sleep command after '&' in a shell script has no effect Product: Busybox Version: unspecified Hardware: Other OS: Linux Status: NEW Severity: blocker Priority: P5 Component: Standard Compliance Assignee: unassigned at busybox.net Reporter: manhongdai at gmail.com CC: busybox-cvs at busybox.net Target Milestone: --- In BusyBox v1.36.1 coming with Alpine Linux 3.18.2 for Raspberry Pi, the script below will return immediately. However, on all old versions of BusyBox, such as v1.35.0 on Alpine Linux 3.17.3 on Raspberry Pi, it will return after 60 seconds. #!/bin/sh for i in 0 1 2 3 4 5 do echo "Loop: $i" & sleep 10 done -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Sat Jun 17 23:48:27 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Sat, 17 Jun 2023 23:48:27 +0000 Subject: [Bug 15631] sleep command after '&' in a shell script has no effect In-Reply-To: References: Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15631 --- Comment #1 from Manhong --- A workaround is to add a pair of parenthesis to the command that need to be in the background. #!/bin/sh for i in 0 1 2 3 4 5 do ( echo "Loop: $i" & ) sleep 10 done -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Sat Jun 17 18:45:02 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 20:45:02 +0200 Subject: [git commit] ash: code shrink Message-ID: <20230618163223.5D3BB84050@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=d0441222db80c6fc0c47fa014106d38e25bfada7 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta setvar 166 164 -2 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 51b627fcc..dde36dd7c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2506,7 +2506,7 @@ setvar(const char *name, const char *val, int flags) p = mempcpy(nameeq, name, namelen); if (val) { *p++ = '='; - memcpy(p, val, vallen); + strcpy(p, val); } vp = setvareq(nameeq, flags | VNOSAVE); INT_ON; From vda.linux at googlemail.com Sun Jun 18 16:12:04 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 18:12:04 +0200 Subject: [git commit] shell/math: decrease stack usage Message-ID: <20230618163223.833E984053@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=182e5a4d000cdb5808830b1f02c59d40c6e61150 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1412 1467 +55 Signed-off-by: Denys Vlasenko --- shell/math.c | 62 ++++++++++++++++++++++++++---------------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/shell/math.c b/shell/math.c index 4b56d1397..f6fed805c 100644 --- a/shell/math.c +++ b/shell/math.c @@ -579,48 +579,40 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) # endif #endif -//TODO: much better estimation than expr_len/2? Such as: -//static unsigned estimate_nums_and_names(const char *expr) -//{ -// unsigned count = 0; -// while (*(expr = skip_whitespace(expr)) != '\0') { -// const char *p; -// if (isdigit(*expr)) { -// while (isdigit(*++expr)) -// continue; -// count++; -// continue; -// } -// p = endofname(expr); -// if (p != expr) { -// expr = p; -// count++; -// continue; -// } -// } -// return count; -//} - static arith_t evaluate_string(arith_state_t *math_state, const char *expr) { - operator lasttok; - const char *errmsg = NULL; - const char *start_expr = expr = skip_whitespace(expr); - unsigned expr_len = strlen(expr) + 2; /* Stack of integers/names */ - /* There can be no more than strlen(startbuf)/2+1 - * integers/names in any given correct or incorrect expression. - * (modulo "09v09v09v09v09v" case, - * but we have code to detect that early) - */ - var_or_num_t *const numstack = alloca((expr_len / 2) * sizeof(numstack[0])); - var_or_num_t *numstackptr = numstack; + var_or_num_t *numstack, *numstackptr; /* Stack of operator tokens */ - operator *const opstack = alloca(expr_len * sizeof(opstack[0])); - operator *opstackptr = opstack; + operator *opstack, *opstackptr; + operator lasttok; operator insert_op = 0xff; unsigned ternary_level = 0; + const char *errmsg; + const char *start_expr = expr = skip_whitespace(expr); + + { + unsigned expr_len = strlen(expr) + 2; + /* If LOTS of whitespace, do not blow up the estimation */ + const char *p = expr; + while (*p) { + /* in a run of whitespace, count only 1st char */ + if (isspace(*p)) { + while (p++, isspace(*p)) + expr_len--; + } else { + p++; + } + } + /* There can be no more than expr_len/2 + * integers/names in any given correct or incorrect expression. + * (modulo "09v09v09v09v09v" case, + * but we have code to detect that early) + */ + numstackptr = numstack = alloca((expr_len / 2) * sizeof(numstack[0])); + opstackptr = opstack = alloca(expr_len * sizeof(opstack[0])); + } /* Start with a left paren */ dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack)); From vda.linux at googlemail.com Sun Jun 18 16:31:55 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 18:31:55 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230618163223.9D6C984054@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=2d06c83b87e8c8330ef82bbdf4314112400877f7 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1489 1486 -3 Signed-off-by: Denys Vlasenko --- shell/math.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/shell/math.c b/shell/math.c index 4c1b1b762..fee325307 100644 --- a/shell/math.c +++ b/shell/math.c @@ -264,10 +264,9 @@ static ALWAYS_INLINE int isalnum_(int c) static arith_t evaluate_string(arith_state_t *math_state, const char *expr); -static const char* -arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) +static arith_t +arith_lookup_val(arith_state_t *math_state, const char *name) { - const char *name = t->var_name; char c; const char *p; char *e = (char*)endofname(name); @@ -277,6 +276,7 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) p = math_state->lookupvar(name); *e = c; if (p) { + arith_t val; size_t len = e - name; remembered_name *cur; remembered_name remember; @@ -289,7 +289,8 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) && !isalnum_(cur->var_name[len]) ) { /* yes */ - return "expression recursion loop detected"; + math_state->errmsg = "expression recursion loop detected"; + return -1; } } @@ -299,16 +300,16 @@ arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) math_state->list_of_recursed_names = &remember; /* recursively evaluate p as expression */ - t->val = evaluate_string(math_state, p); + /* this sets math_state->errmsg on error */ + val = evaluate_string(math_state, p); /* pop current var name */ math_state->list_of_recursed_names = remember.next; - return math_state->errmsg; + return val; } /* treat undefined var as 0 */ - t->val = 0; - return NULL; + return 0; } /* "Applying" a token means performing it on the top elements on the integer @@ -684,9 +685,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) || expr[1] == '=' /* or "==..." */ ) { /* Evaluate variable to value */ - errmsg = arith_lookup_val(math_state, numstackptr); - if (errmsg) - goto err_with_custom_msg; + numstackptr->val = arith_lookup_val(math_state, numstackptr->var_name); + if (math_state->errmsg) + return numstackptr->val; /* -1 */ } } else { dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); From vda.linux at googlemail.com Sat Jun 17 19:13:13 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sat, 17 Jun 2023 21:13:13 +0200 Subject: [git commit] shell: move varcmp() to shell_common.h and use it in hush Message-ID: <20230618163223.775DA84052@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=96769486e20fd5f1142cae0db2cbacef31dc75e9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta unset_local_var - 112 +112 findvar 31 35 +4 set_vars_and_save_old 144 141 -3 helper_export_local 235 230 -5 set_local_var 425 416 -9 handle_changed_special_names 38 27 -11 builtin_unset 154 141 -13 builtin_getopts 404 391 -13 get_local_var_value 281 260 -21 get_ptr_to_local_var 71 45 -26 unset_local_var_len 139 - -139 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 1/8 up/down: 116/-240) Total: -124 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 24 ------------------------ shell/hush.c | 42 ++++++++++++++++-------------------------- shell/shell_common.c | 19 +++++++++++++++++++ shell/shell_common.h | 2 ++ 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index dde36dd7c..96d2433d3 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2259,30 +2259,6 @@ getoptsreset(const char *value) } #endif -/* - * Compares two strings up to the first = or '\0'. The first - * string must be terminated by '='; the second may be terminated by - * either '=' or '\0'. - */ -static int -varcmp(const char *p, const char *q) -{ - int c, d; - - while ((c = *p) == (d = *q)) { - if (c == '\0' || c == '=') - goto out; - p++; - q++; - } - if (c == '=') - c = '\0'; - if (d == '=') - d = '\0'; - out: - return c - d; -} - /* * Find the appropriate entry in the hash table from the name. */ diff --git a/shell/hush.c b/shell/hush.c index dbc4aecab..426182924 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2255,14 +2255,14 @@ static const char *get_cwd(int force) /* * Shell and environment variable support */ -static struct variable **get_ptr_to_local_var(const char *name, unsigned len) +static struct variable **get_ptr_to_local_var(const char *name) { struct variable **pp; struct variable *cur; pp = &G.top_var; while ((cur = *pp) != NULL) { - if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=') + if (varcmp(cur->varstr, name) == 0) return pp; pp = &cur->next; } @@ -2272,21 +2272,20 @@ static struct variable **get_ptr_to_local_var(const char *name, unsigned len) static const char* FAST_FUNC get_local_var_value(const char *name) { struct variable **vpp; - unsigned len = strlen(name); if (G.expanded_assignments) { char **cpp = G.expanded_assignments; while (*cpp) { char *cp = *cpp; - if (strncmp(cp, name, len) == 0 && cp[len] == '=') - return cp + len + 1; + if (varcmp(cp, name) == 0) + return strchr(cp, '=') + 1; cpp++; } } - vpp = get_ptr_to_local_var(name, len); + vpp = get_ptr_to_local_var(name); if (vpp) - return (*vpp)->varstr + len + 1; + return strchr((*vpp)->varstr, '=') + 1; if (strcmp(name, "PPID") == 0) return utoa(G.root_ppid); @@ -2319,13 +2318,11 @@ static const char* FAST_FUNC get_local_var_value(const char *name) } #if ENABLE_HUSH_GETOPTS -static void handle_changed_special_names(const char *name, unsigned name_len) +static void handle_changed_special_names(const char *name) { - if (name_len == 6) { - if (strncmp(name, "OPTIND", 6) == 0) { - G.getopt_count = 0; - return; - } + if (varcmp(name, "OPTIND") == 0) { + G.getopt_count = 0; + return; } } #else @@ -2476,7 +2473,7 @@ static int set_local_var(char *str, unsigned flags) } free(free_me); - handle_changed_special_names(cur->varstr, name_len - 1); + handle_changed_special_names(cur->varstr); return retval; } @@ -2499,16 +2496,14 @@ static void set_pwd_var(unsigned flag) } #if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS -static int unset_local_var_len(const char *name, int name_len) +static int unset_local_var(const char *name) { struct variable *cur; struct variable **cur_pp; cur_pp = &G.top_var; while ((cur = *cur_pp) != NULL) { - if (strncmp(cur->varstr, name, name_len) == 0 - && cur->varstr[name_len] == '=' - ) { + if (varcmp(cur->varstr, name) == 0) { if (cur->flg_read_only) { bb_error_msg("%s: readonly variable", name); return EXIT_FAILURE; @@ -2527,15 +2522,10 @@ static int unset_local_var_len(const char *name, int name_len) } /* Handle "unset LINENO" et al even if did not find the variable to unset */ - handle_changed_special_names(name, name_len); + handle_changed_special_names(name); return EXIT_SUCCESS; } - -static int unset_local_var(const char *name) -{ - return unset_local_var_len(name, strlen(name)); -} #endif @@ -2581,7 +2571,7 @@ static void set_vars_and_save_old(char **strings) eq = strchr(*s, '='); if (HUSH_DEBUG && !eq) bb_simple_error_msg_and_die("BUG in varexp4"); - var_pp = get_ptr_to_local_var(*s, eq - *s); + var_pp = get_ptr_to_local_var(*s); if (var_pp) { var_p = *var_pp; if (var_p->flg_read_only) { @@ -11215,7 +11205,7 @@ static int helper_export_local(char **argv, unsigned flags) if (*name_end == '\0') { struct variable *var, **vpp; - vpp = get_ptr_to_local_var(name, name_end - name); + vpp = get_ptr_to_local_var(name); var = vpp ? *vpp : NULL; if (flags & SETFLAG_UNEXPORT) { diff --git a/shell/shell_common.c b/shell/shell_common.c index 1eca101b9..e5c2cefb3 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -22,6 +22,25 @@ const char defifsvar[] ALIGN1 = "IFS= \t\n"; const char defoptindvar[] ALIGN1 = "OPTIND=1"; +/* Compare two strings up to the first '=' or '\0'. */ +int FAST_FUNC varcmp(const char *p, const char *q) +{ + int c, d; + + while ((c = *p) == (d = *q)) { + if (c == '\0' || c == '=') + goto out; + p++; + q++; + } + if (c == '=') + c = '\0'; + if (d == '=') + d = '\0'; + out: + return c - d; +} + /* read builtin */ /* Needs to be interruptible: shell must handle traps and shell-special signals diff --git a/shell/shell_common.h b/shell/shell_common.h index 7b478f1df..fab676e4a 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h @@ -26,6 +26,8 @@ extern const char defifsvar[] ALIGN1; /* "IFS= \t\n" */ extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */ +int FAST_FUNC varcmp(const char *p, const char *q); + /* Builtins */ struct builtin_read_params { From vda.linux at googlemail.com Sun Jun 18 16:12:14 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 18:12:14 +0200 Subject: [git commit] shell/math: decrease stack usage by not allocating copies of variable names Message-ID: <20230618163223.90E1D84050@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=c72c5552edecb8a2f97cd7e2d9a09a3059986cca branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master We risk exhaust stack with alloca() with old code. function old new delta arith_apply 990 1023 +33 evaluate_string 1467 1494 +27 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 60/0) Total: 60 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 83 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/shell/math.c b/shell/math.c index f6fed805c..4c1b1b762 100644 --- a/shell/math.c +++ b/shell/math.c @@ -245,7 +245,7 @@ is_right_associative(operator prec) typedef struct { arith_t val; - char *var_name; + const char *var_name; } var_or_num_t; #define VALID_NAME(name) (name) @@ -256,44 +256,58 @@ typedef struct remembered_name { const char *var_name; } remembered_name; +static ALWAYS_INLINE int isalnum_(int c) +{ + return (isalnum(c) || c == '_'); +} + static arith_t evaluate_string(arith_state_t *math_state, const char *expr); static const char* arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) { - if (VALID_NAME(t->var_name)) { - const char *p = math_state->lookupvar(t->var_name); - if (p) { - remembered_name *cur; - remembered_name remember; - - /* did we already see this name? - * testcase: a=b; b=a; echo $((a)) - */ - for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) { - if (strcmp(cur->var_name, t->var_name) == 0) { - /* yes */ - return "expression recursion loop detected"; - } + const char *name = t->var_name; + char c; + const char *p; + char *e = (char*)endofname(name); + + c = *e; + *e = '\0'; + p = math_state->lookupvar(name); + *e = c; + if (p) { + size_t len = e - name; + remembered_name *cur; + remembered_name remember; + + /* did we already see this name? + * testcase: a=b; b=a; echo $((a)) + */ + for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) { + if (strncmp(cur->var_name, name, len) == 0 + && !isalnum_(cur->var_name[len]) + ) { + /* yes */ + return "expression recursion loop detected"; } + } - /* push current var name */ - remember.var_name = t->var_name; - remember.next = math_state->list_of_recursed_names; - math_state->list_of_recursed_names = &remember; + /* push current var name */ + remember.var_name = name; + remember.next = math_state->list_of_recursed_names; + math_state->list_of_recursed_names = &remember; - /* recursively evaluate p as expression */ - t->val = evaluate_string(math_state, p); + /* recursively evaluate p as expression */ + t->val = evaluate_string(math_state, p); - /* pop current var name */ - math_state->list_of_recursed_names = remember.next; + /* pop current var name */ + math_state->list_of_recursed_names = remember.next; - return math_state->errmsg; - } - /* treat undefined var as 0 */ - t->val = 0; + return math_state->errmsg; } + /* treat undefined var as 0 */ + t->val = 0; return NULL; } @@ -447,7 +461,13 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ } /* Save to shell variable */ sprintf(buf, ARITH_FMT, rez); - math_state->setvar(top_of_stack->var_name, buf); + { + char *e = (char*)endofname(top_of_stack->var_name); + char c = *e; + *e = '\0'; + math_state->setvar(top_of_stack->var_name, buf); + *e = c; + } /* After saving, make previous value for v++ or v-- */ if (op == TOK_POST_INC) rez--; @@ -610,6 +630,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) * (modulo "09v09v09v09v09v" case, * but we have code to detect that early) */ + dbg("expr:'%s' expr_len:%u", expr, expr_len); numstackptr = numstack = alloca((expr_len / 2) * sizeof(numstack[0])); opstackptr = opstack = alloca(expr_len * sizeof(opstack[0])); } @@ -655,10 +676,8 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (p != expr) { /* Name */ if (!math_state->evaluation_disabled) { - size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ - numstackptr->var_name = alloca(var_name_size); - safe_strncpy(numstackptr->var_name, expr, var_name_size); - dbg("[%d] var:'%s'", (int)(numstackptr - numstack), numstackptr->var_name); + numstackptr->var_name = expr; + dbg("[%d] var:'%.*s'", (int)(numstackptr - numstack), (int)(p - expr), expr); expr = skip_whitespace(p); /* If it is not followed by "=" operator... */ if (expr[0] != '=' /* not "=..." */ From vda.linux at googlemail.com Sun Jun 18 16:49:00 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 18:49:00 +0200 Subject: [git commit] shell: typo fix in tests Message-ID: <20230618164914.A8DFC84063@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=b61fd8ec5a2e3b728c05a72b603fb4255b1022da branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-ternary_nested3.tests | 2 +- shell/hush_test/hush-arith/arith-ternary_nested3.tests | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-ternary_nested3.tests b/shell/ash_test/ash-arith/arith-ternary_nested3.tests index b69dcc6e9..6f753867e 100755 --- a/shell/ash_test/ash-arith/arith-ternary_nested3.tests +++ b/shell/ash_test/ash-arith/arith-ternary_nested3.tests @@ -1,6 +1,6 @@ exec 2>&1 x='@' a=2 -# After processing nested ?:, outermost ?: should still rememeber to NOT evaluate a*=2 +# After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2 echo 42:$((1?0?41:42:(a*=2))) echo "a=2:$a" diff --git a/shell/hush_test/hush-arith/arith-ternary_nested3.tests b/shell/hush_test/hush-arith/arith-ternary_nested3.tests index b69dcc6e9..6f753867e 100755 --- a/shell/hush_test/hush-arith/arith-ternary_nested3.tests +++ b/shell/hush_test/hush-arith/arith-ternary_nested3.tests @@ -1,6 +1,6 @@ exec 2>&1 x='@' a=2 -# After processing nested ?:, outermost ?: should still rememeber to NOT evaluate a*=2 +# After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2 echo 42:$((1?0?41:42:(a*=2))) echo "a=2:$a" From vda.linux at googlemail.com Sun Jun 18 17:03:05 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 19:03:05 +0200 Subject: [git commit] shell/math: eliminate redundant endofname() Message-ID: <20230618170337.795DE84077@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=8309c9159f7d8ee8b0f6f4b401750c5a03a4b0b9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1486 1498 +12 Signed-off-by: Denys Vlasenko --- shell/math.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shell/math.c b/shell/math.c index fee325307..cb702b7f4 100644 --- a/shell/math.c +++ b/shell/math.c @@ -265,19 +265,18 @@ static arith_t evaluate_string(arith_state_t *math_state, const char *expr); static arith_t -arith_lookup_val(arith_state_t *math_state, const char *name) +arith_lookup_val(arith_state_t *math_state, const char *name, char *endname) { char c; const char *p; - char *e = (char*)endofname(name); - c = *e; - *e = '\0'; + c = *endname; + *endname = '\0'; p = math_state->lookupvar(name); - *e = c; + *endname = c; if (p) { arith_t val; - size_t len = e - name; + size_t len = endname - name; remembered_name *cur; remembered_name remember; @@ -685,9 +684,10 @@ evaluate_string(arith_state_t *math_state, const char *expr) || expr[1] == '=' /* or "==..." */ ) { /* Evaluate variable to value */ - numstackptr->val = arith_lookup_val(math_state, numstackptr->var_name); + arith_t val = arith_lookup_val(math_state, numstackptr->var_name, (char*)p); if (math_state->errmsg) - return numstackptr->val; /* -1 */ + return val; /* -1 */ + numstackptr->val = val; } } else { dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); From vda.linux at googlemail.com Sun Jun 18 17:20:10 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 19:20:10 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230618172449.1A40C8408A@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=7701b526a720c4a84839174fe6b084d831ac90c9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1498 1491 -7 Signed-off-by: Denys Vlasenko --- shell/math.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shell/math.c b/shell/math.c index cb702b7f4..6784eeeb0 100644 --- a/shell/math.c +++ b/shell/math.c @@ -363,7 +363,6 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ /* Pop numstack */ NUMSTACKPTR = top_of_stack; /* this decrements NUMSTACKPTR */ - top_of_stack--; /* now points to left side */ if (math_state->evaluation_disabled) { dbg("binary op %02x skipped", op); @@ -375,6 +374,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ */ } + top_of_stack--; /* now points to left side */ right_side_val = rez; rez = top_of_stack->val; if (op == TOK_BOR || op == TOK_OR_ASSIGN) @@ -703,9 +703,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) if (isdigit(*expr)) { /* Number */ + char *end; numstackptr->var_name = NULL; errno = 0; - numstackptr->val = strto_arith_t(expr, (char**) &expr); + end = (char*) expr; /* separate variable to go on stack */ + numstackptr->val = strto_arith_t(expr, &end); + expr = end; dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); /* A number can't be followed by another number, or a variable name. * We'd catch this later anyway, but this would require numstack[] From vda.linux at googlemail.com Sun Jun 18 17:30:22 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 19:30:22 +0200 Subject: [git commit] shell/math: explain why we use separate &end Message-ID: <20230618173118.9C136840E5@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=10cce8ae35654585a09d6e839dd502f04b81599d branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/math.c b/shell/math.c index 6784eeeb0..0b30e089d 100644 --- a/shell/math.c +++ b/shell/math.c @@ -706,7 +706,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) char *end; numstackptr->var_name = NULL; errno = 0; - end = (char*) expr; /* separate variable to go on stack */ + /* code is smaller compared to using &expr here: */ numstackptr->val = strto_arith_t(expr, &end); expr = end; dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); From bugzilla at busybox.net Sun Jun 18 17:47:54 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Sun, 18 Jun 2023 17:47:54 +0000 Subject: [Bug 15640] New: diff -r without -N produces misleading results Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15640 Bug ID: 15640 Summary: diff -r without -N produces misleading results Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: yuri.kanivetsky at gmail.com CC: busybox-cvs at busybox.net Target Milestone: --- The following commit: https://git.busybox.net/busybox/commit/?id=75703eb8d5bd0d8d9ef0a8abaf25b421ca668ab5 makes diff not recurse into dirs which do not exist on the "other side." Which gives a false sense that what differs is only what is reported. You should at least give some warning, I believe. Also the following comment https://git.busybox.net/busybox/commit/?id=f18a82d9b320146e367217aba6664b1efb1be68c Initially I thought that diffutils skips such dirs too, but apparently it doesn't. I'm running busybox-1.36.0, just in case. It's not in the list of the versions. -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Sun Jun 18 18:15:35 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 18 Jun 2023 20:15:35 +0200 Subject: [git commit] shell/math: add note on ERANGE Message-ID: <20230618181559.D0FAA8411F@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=79b90cbece5d69c4d370347c347f3d17bd1156c6 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1488 1478 -10 Signed-off-by: Denys Vlasenko --- shell/math.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/shell/math.c b/shell/math.c index 0b30e089d..e5447e767 100644 --- a/shell/math.c +++ b/shell/math.c @@ -667,6 +667,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* At this point, we're done with the expression */ if (numstackptr != numstack + 1) { /* if there is not exactly one result, it's bad */ + /* Example: $((1 2)) */ goto syntax_err; } return numstack->val; @@ -691,9 +692,10 @@ evaluate_string(arith_state_t *math_state, const char *expr) } } else { dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); - numstackptr->var_name = NULL; - numstackptr->val = 0; //paranoia, probably not needed expr = p; + numstackptr->var_name = NULL; + push_num0: + numstackptr->val = 0; } push_num: numstackptr++; @@ -717,8 +719,13 @@ evaluate_string(arith_state_t *math_state, const char *expr) */ if (isalnum(*expr) || *expr == '_') goto syntax_err; - if (errno) - numstackptr->val = 0; /* bash compat */ + if (errno) { +// TODO: bash 5.2.15 does not catch ERANGE (some older version did?). +// $((99999999999999999999)) is 7766279631452241919 (the 64-bit truncated value). +// Our BASE# code does this as well: try $((10#99999999999999999999)), +// but the "ordinary" code path with strtoull() does not do this. + goto push_num0; /* bash compat */ + } goto push_num; } From vda.linux at googlemail.com Mon Jun 19 07:44:04 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 19 Jun 2023 09:44:04 +0200 Subject: [git commit] shell/math: fix comments about jammed-together num+num corner cases Message-ID: <20230619074636.CE1418419B@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=a165098922ddbfbca3579538f50b44002ab0fae7 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta evaluate_string 1478 1470 -8 Signed-off-by: Denys Vlasenko --- shell/math.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/shell/math.c b/shell/math.c index e5447e767..d9986335a 100644 --- a/shell/math.c +++ b/shell/math.c @@ -613,7 +613,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) const char *start_expr = expr = skip_whitespace(expr); { - unsigned expr_len = strlen(expr) + 2; + unsigned expr_len = strlen(expr); /* If LOTS of whitespace, do not blow up the estimation */ const char *p = expr; while (*p) { @@ -625,14 +625,21 @@ evaluate_string(arith_state_t *math_state, const char *expr) p++; } } - /* There can be no more than expr_len/2 + dbg("expr:'%s' expr_len:%u", expr, expr_len); + /* expr_len deep opstack is needed. Think "------------7". + * Only "?" operator temporarily needs two opstack slots + * (IOW: more than one slot), but its second slot (LPAREN) + * is popped off when ":" is reached. + */ + opstackptr = opstack = alloca(expr_len * sizeof(opstack[0])); + /* There can be no more than (expr_len/2 + 1) * integers/names in any given correct or incorrect expression. - * (modulo "09v09v09v09v09v" case, + * (modulo "09", "0v" cases where 2 chars are 2 ints/names, * but we have code to detect that early) */ - dbg("expr:'%s' expr_len:%u", expr, expr_len); - numstackptr = numstack = alloca((expr_len / 2) * sizeof(numstack[0])); - opstackptr = opstack = alloca(expr_len * sizeof(opstack[0])); + expr_len = (expr_len / 2) + + 1 /* "1+2" has two nums, 2 = len/2+1, NOT len/2 */; + numstackptr = numstack = alloca(expr_len * sizeof(numstack[0])); } /* Start with a left paren */ @@ -714,8 +721,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); /* A number can't be followed by another number, or a variable name. * We'd catch this later anyway, but this would require numstack[] - * to be twice as deep to handle strings where _every_ char is - * a new number or name. Example: 09v09v09v09v09v09v09v09v09v + * to be ~twice as deep to handle strings where _every_ char is + * a new number or name. + * Examples: "09" is two numbers, "0v" is number and name. */ if (isalnum(*expr) || *expr == '_') goto syntax_err; From vda.linux at googlemail.com Mon Jun 19 08:22:15 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 19 Jun 2023 10:22:15 +0200 Subject: [git commit] shell/math: $((1?)) has one-too-small opstack, fix this Message-ID: <20230619082307.A2D68841F4@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=db0e886735112110183fe3e4f1dea7c0f31a36c9 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/math.c b/shell/math.c index d9986335a..0cf963731 100644 --- a/shell/math.c +++ b/shell/math.c @@ -114,7 +114,7 @@ #include "libbb.h" #include "math.h" -#if 1 +#if 0 # define dbg(...) ((void)0) #else # define dbg(...) bb_error_msg(__VA_ARGS__) @@ -631,6 +631,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) * (IOW: more than one slot), but its second slot (LPAREN) * is popped off when ":" is reached. */ + expr_len++; /* +1 for 1st LPAREN. See what $((1?)) pushes to opstack */ opstackptr = opstack = alloca(expr_len * sizeof(opstack[0])); /* There can be no more than (expr_len/2 + 1) * integers/names in any given correct or incorrect expression. From vda.linux at googlemail.com Mon Jun 19 08:23:58 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 19 Jun 2023 10:23:58 +0200 Subject: [git commit] shell/math: disable debug again Message-ID: <20230619082405.C2F218423D@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=019dd31150526b7dd9dd0addfc38f08bcf7ec551 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/math.c b/shell/math.c index 0cf963731..ac758639f 100644 --- a/shell/math.c +++ b/shell/math.c @@ -114,7 +114,7 @@ #include "libbb.h" #include "math.h" -#if 0 +#if 1 # define dbg(...) ((void)0) #else # define dbg(...) bb_error_msg(__VA_ARGS__) From bugzilla at busybox.net Wed Jun 21 14:00:27 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Wed, 21 Jun 2023 14:00:27 +0000 Subject: [Bug 15646] New: [busybox 1.36.1] heap-buffer-overflow in bc Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15646 Bug ID: 15646 Summary: [busybox 1.36.1] heap-buffer-overflow in bc Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: f.busse at imperial.ac.uk CC: busybox-cvs at busybox.net Target Milestone: --- Sending a _very_ specific string to bc results in a heap overflow: $ printf 'con\x00ti\x00n\x00ue' | busybox-1.36.1/bin/busybox bc =441==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x611000000cb8 at pc 0x558e97256997 bp 0x7ffde2d76a10 sp 0x7ffde2d76a00 READ of size 8 at 0x611000000cb8 thread T0 #0 0x558e97256996 in zbc_parse_break_or_continue miscutils/bc.c:4428 #1 0x558e97256996 in zbc_parse_stmt_possibly_auto miscutils/bc.c:4717 0x611000000cb8 is located 8 bytes to the left of 256-byte region [0x611000000cc0,0x611000000dc0) allocated by thread T0 here: #0 0x7f6629884867 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145 #1 0x558e9731ec5d in xmalloc libbb/xfuncs_printf.c:50 SUMMARY: AddressSanitizer: heap-buffer-overflow miscutils/bc.c:4428 in zbc_parse_break_or_continue Shadow bytes around the buggy address: 0x0c227fff8140: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c227fff8150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c227fff8160: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa 0x0c227fff8170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c227fff8180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c227fff8190: fa fa fa fa fa fa fa[fa]00 00 00 00 00 00 00 00 0x0c227fff81a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c227fff81b0: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa 0x0c227fff81c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fff81d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fff81e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa (found by KLEE) -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Wed Jun 21 14:26:43 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Wed, 21 Jun 2023 14:26:43 +0000 Subject: [Bug 15649] New: [busybox 1.36.1] global-buffer-overflow in od Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15649 Bug ID: 15649 Summary: [busybox 1.36.1] global-buffer-overflow in od Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: f.busse at imperial.ac.uk CC: busybox-cvs at busybox.net Target Milestone: --- Passing "-An" as argument results in an out-of-bound access in od: $ /tmp/root/busybox-1.36.1/bin/busybox od -An coreutils/od_bloaty.c:1236:45: runtime error: index 3 out of bounds for type 'char [3]' coreutils/od_bloaty.c:1236:45: runtime error: load of address 0x55e512d67703 with insufficient space for an object of type 'const char' 0x55e512d67703: note: pointer points here 00 75 6f 78 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ^ ================================================================= ==457==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55e512d67703 at pc 0x55e512cfd867 bp 0x7ffc45c79130 sp 0x7ffc45c79120 READ of size 1 at 0x55e512d67703 thread T0 #0 0x55e512cfd866 in od_main coreutils/od_bloaty.c:1236 0x55e512d67703 is located 61 bytes to the left of global variable 'doxn' defined in 'coreutils/od_bloaty.c:1221:21' (0x55e512d67740) of size 5 'doxn' is ascii string 'doxn' 0x55e512d67703 is located 0 bytes to the right of global variable 'doxn_address_base_char' defined in 'coreutils/od_bloaty.c:1222:21' (0x55e512d67700) of size 3 SUMMARY: AddressSanitizer: global-buffer-overflow coreutils/od_bloaty.c:1236 in od_main Shadow bytes around the buggy address: 0x0abd225a4e90: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x0abd225a4ea0: 04 f9 f9 f9 f9 f9 f9 f9 00 00 f9 f9 f9 f9 f9 f9 0x0abd225a4eb0: 00 00 f9 f9 f9 f9 f9 f9 05 f9 f9 f9 f9 f9 f9 f9 0x0abd225a4ec0: 04 f9 f9 f9 f9 f9 f9 f9 05 f9 f9 f9 f9 f9 f9 f9 0x0abd225a4ed0: 00 00 00 f9 f9 f9 f9 f9 03 f9 f9 f9 f9 f9 f9 f9 =>0x0abd225a4ee0:[03]f9 f9 f9 f9 f9 f9 f9 05 f9 f9 f9 f9 f9 f9 f9 0x0abd225a4ef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 0x0abd225a4f00: f9 f9 f9 f9 00 00 01 f9 f9 f9 f9 f9 00 01 f9 f9 0x0abd225a4f10: f9 f9 f9 f9 00 01 f9 f9 f9 f9 f9 f9 00 00 01 f9 0x0abd225a4f20: f9 f9 f9 f9 00 00 01 f9 f9 f9 f9 f9 00 00 01 f9 0x0abd225a4f30: f9 f9 f9 f9 00 00 01 f9 f9 f9 f9 f9 02 f9 f9 f9 (found by KLEE) -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Thu Jun 22 15:22:02 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Thu, 22 Jun 2023 15:22:02 +0000 Subject: [Bug 15652] New: [busybox 1.36.1] heap-use-after-free in tsort Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15652 Bug ID: 15652 Summary: [busybox 1.36.1] heap-use-after-free in tsort Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: f.busse at imperial.ac.uk CC: busybox-cvs at busybox.net Target Milestone: --- The following input causes a use-after-free: $ printf '\x0f\n\xf0\n\xf0\n\x0f' | busybox-1.36.1/bin/busybox tsort ==2165==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000040 at pc 0x560d7ee21afd bp 0x7fff70e3f840 sp 0x7fff70e3f830 READ of size 4 at 0x603000000040 thread T0 #0 0x560d7ee21afc in tsort_main coreutils/tsort.c:179 (found be KLEE) -- You are receiving this mail because: You are on the CC list for the bug. From bugzilla at busybox.net Thu Jun 22 15:52:54 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Thu, 22 Jun 2023 15:52:54 +0000 Subject: [Bug 15655] New: [busybox 1.36.1] segmentation fault in awk Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15655 Bug ID: 15655 Summary: [busybox 1.36.1] segmentation fault in awk Product: Busybox Version: unspecified Hardware: All OS: Linux Status: NEW Severity: normal Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: f.busse at imperial.ac.uk CC: busybox-cvs at busybox.net Target Milestone: --- The following parameter $ busybox-1.36.1/bin/busybox" "awk" "-f/" leads to a segfault in awk, skip_spaces gets a NULL pointer: #0 0x00005555557c8d6f in skip_spaces (p=p at entry=0x0) at editors/awk.c:826 #1 0x00005555557cc649 in next_token (expected=expected at entry=2146997285) at editors/awk.c:1132 #2 0x00005555557d4a39 in parse_program (p=p at entry=0x0) at editors/awk.c:1720 #3 0x00005555557dc848 in awk_main (argc=, argv=) at editors/awk.c:3660 #4 0x0000555555725260 in run_applet_no_and_exit (applet_no=applet_no at entry=2, name=name at entry=0x7fffffffe699 "awk", argv=argv at entry=0x7fffffffe3b0) at libbb/appletlib.c:969 #5 0x000055555572577d in run_applet_and_exit (name=name at entry=0x7fffffffe699 "awk", argv=argv at entry=0x7fffffffe3b0) at libbb/appletlib.c:988 #6 0x0000555555725752 in busybox_main (argv=0x7fffffffe3b0) at libbb/appletlib.c:916 #7 run_applet_and_exit (name=name at entry=0x7fffffffe691 "busybox", argv=argv at entry=0x7fffffffe3a8) at libbb/appletlib.c:981 #8 0x000055555572589b in main (argc=, argv=0x7fffffffe3a8) at libbb/appletlib.c:1128 (found by KLEE) -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Sun Jun 25 15:42:05 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Sun, 25 Jun 2023 17:42:05 +0200 Subject: [git commit] shell/math: bash-compatible handling of too large numbers Message-ID: <20230625154501.BA0E384B96@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=c1c267fd36b0fcac8c8871232eecc1e360173990 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta parse_with_base - 170 +170 evaluate_string 1477 1309 -168 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 170/-168) Total: 2 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-arith/arith-bignum1.right | 13 ++++ shell/ash_test/ash-arith/arith-bignum1.tests | 17 ++++++ shell/ash_test/ash-arith/arith.right | 3 +- shell/ash_test/ash-arith/arith.tests | 6 +- shell/hush_test/hush-arith/arith-bignum1.right | 13 ++++ shell/hush_test/hush-arith/arith-bignum1.tests | 17 ++++++ shell/hush_test/hush-arith/arith.right | 3 +- shell/hush_test/hush-arith/arith.tests | 6 +- shell/math.c | 84 +++++++++++++++----------- 9 files changed, 118 insertions(+), 44 deletions(-) diff --git a/shell/ash_test/ash-arith/arith-bignum1.right b/shell/ash_test/ash-arith/arith-bignum1.right new file mode 100644 index 000000000..42a8016ec --- /dev/null +++ b/shell/ash_test/ash-arith/arith-bignum1.right @@ -0,0 +1,13 @@ +18 digits: 999999999999999999 +19 digits: -8446744073709551617 +20 digits: 7766279631452241919 +18 digits- -999999999999999999 +19 digits- 8446744073709551617 +20 digits- -7766279631452241919 +Hex base#: +16 digits: 9876543210abcedf +17 digits: 876543210abcedfc +18 digits: 76543210abcedfcc +16 digits: 6789abcdef543121 +17 digits: 789abcdef5431204 +18 digits: 89abcdef54312034 diff --git a/shell/ash_test/ash-arith/arith-bignum1.tests b/shell/ash_test/ash-arith/arith-bignum1.tests new file mode 100755 index 000000000..ef8f928bc --- /dev/null +++ b/shell/ash_test/ash-arith/arith-bignum1.tests @@ -0,0 +1,17 @@ +exec 2>&1 +# If the number does not fit in 64 bits, bash uses truncated 64-bit value +# (essentially, it does not check for overflow in "n = n * base + digit" +# calculation). +echo 18 digits: $((999999999999999999)) +echo 19 digits: $((9999999999999999999)) +echo 20 digits: $((99999999999999999999)) +echo 18 digits- $((-999999999999999999)) +echo 19 digits- $((-9999999999999999999)) +echo 20 digits- $((-99999999999999999999)) +echo "Hex base#:" +printf '16 digits: %016x\n' $((16#9876543210abcedf)) +printf '17 digits: %016x\n' $((16#9876543210abcedfc)) +printf '18 digits: %016x\n' $((16#9876543210abcedfcc)) +printf '16 digits: %016x\n' $((-16#9876543210abcedf)) +printf '17 digits: %016x\n' $((-16#9876543210abcedfc)) +printf '18 digits: %016x\n' $((-16#9876543210abcedfcc)) diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right index 8bc78b8d1..b2c3f56d8 100644 --- a/shell/ash_test/ash-arith/arith.right +++ b/shell/ash_test/ash-arith/arith.right @@ -80,8 +80,9 @@ other bases 62 62 63 63 missing number after base -0 0 +./arith.tests: line 161: arithmetic syntax error ./arith.tests: line 162: arithmetic syntax error +./arith.tests: line 163: arithmetic syntax error ./arith.tests: line 164: divide by zero ./arith.tests: let: line 165: arithmetic syntax error ./arith.tests: line 166: arithmetic syntax error diff --git a/shell/ash_test/ash-arith/arith.tests b/shell/ash_test/ash-arith/arith.tests index b9cb8ba4c..42cd7fdbf 100755 --- a/shell/ash_test/ash-arith/arith.tests +++ b/shell/ash_test/ash-arith/arith.tests @@ -155,12 +155,12 @@ echo 63 $(( 64#_ )) #ash# # weird bases (error) #ash# echo $(( 3425#56 )) -echo missing number after base -echo 0 $(( 2# )) # these should generate errors +echo missing number after base +( echo $(( 2# )) ) ( echo $(( 7 = 43 )) ) -#ash# echo $(( 2#44 )) +( echo $(( 2#44 )) ) ( echo $(( 44 / 0 )) ) ( let 'jv += $iv' ) ( echo $(( jv += \$iv )) ) diff --git a/shell/hush_test/hush-arith/arith-bignum1.right b/shell/hush_test/hush-arith/arith-bignum1.right new file mode 100644 index 000000000..42a8016ec --- /dev/null +++ b/shell/hush_test/hush-arith/arith-bignum1.right @@ -0,0 +1,13 @@ +18 digits: 999999999999999999 +19 digits: -8446744073709551617 +20 digits: 7766279631452241919 +18 digits- -999999999999999999 +19 digits- 8446744073709551617 +20 digits- -7766279631452241919 +Hex base#: +16 digits: 9876543210abcedf +17 digits: 876543210abcedfc +18 digits: 76543210abcedfcc +16 digits: 6789abcdef543121 +17 digits: 789abcdef5431204 +18 digits: 89abcdef54312034 diff --git a/shell/hush_test/hush-arith/arith-bignum1.tests b/shell/hush_test/hush-arith/arith-bignum1.tests new file mode 100755 index 000000000..ef8f928bc --- /dev/null +++ b/shell/hush_test/hush-arith/arith-bignum1.tests @@ -0,0 +1,17 @@ +exec 2>&1 +# If the number does not fit in 64 bits, bash uses truncated 64-bit value +# (essentially, it does not check for overflow in "n = n * base + digit" +# calculation). +echo 18 digits: $((999999999999999999)) +echo 19 digits: $((9999999999999999999)) +echo 20 digits: $((99999999999999999999)) +echo 18 digits- $((-999999999999999999)) +echo 19 digits- $((-9999999999999999999)) +echo 20 digits- $((-99999999999999999999)) +echo "Hex base#:" +printf '16 digits: %016x\n' $((16#9876543210abcedf)) +printf '17 digits: %016x\n' $((16#9876543210abcedfc)) +printf '18 digits: %016x\n' $((16#9876543210abcedfcc)) +printf '16 digits: %016x\n' $((-16#9876543210abcedf)) +printf '17 digits: %016x\n' $((-16#9876543210abcedfc)) +printf '18 digits: %016x\n' $((-16#9876543210abcedfcc)) diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right index df8154f97..d3a978611 100644 --- a/shell/hush_test/hush-arith/arith.right +++ b/shell/hush_test/hush-arith/arith.right @@ -82,7 +82,8 @@ other bases 62 62 63 63 missing number after base -0 0 +hush: arithmetic syntax error +hush: arithmetic syntax error hush: arithmetic syntax error hush: divide by zero hush: can't execute 'let': No such file or directory diff --git a/shell/hush_test/hush-arith/arith.tests b/shell/hush_test/hush-arith/arith.tests index 6b707486c..9f0399816 100755 --- a/shell/hush_test/hush-arith/arith.tests +++ b/shell/hush_test/hush-arith/arith.tests @@ -159,12 +159,12 @@ echo 63 $(( 64#_ )) #ash# # weird bases (error) #ash# echo $(( 3425#56 )) -echo missing number after base -echo 0 $(( 2# )) # these should generate errors +echo missing number after base +( echo $(( 2# )) ) ( echo $(( 7 = 43 )) ) -#ash# echo $(( 2#44 )) +( echo $(( 2#44 )) ) ( echo $(( 44 / 0 )) ) ( let 'jv += $iv' ) ( echo $(( jv += \$iv )) ) diff --git a/shell/math.c b/shell/math.c index ac758639f..fbf5c587e 100644 --- a/shell/math.c +++ b/shell/math.c @@ -531,29 +531,11 @@ static const char op_tokens[] ALIGN1 = { #define END_POINTER (&op_tokens[sizeof(op_tokens)-1]) #if ENABLE_FEATURE_SH_MATH_BASE -static arith_t strto_arith_t(const char *nptr, char **endptr) +static arith_t parse_with_base(const char *nptr, char **endptr, unsigned base) { - unsigned base; - arith_t n; - -# if ENABLE_FEATURE_SH_MATH_64 - n = strtoull(nptr, endptr, 0); -# else - n = strtoul(nptr, endptr, 0); -# endif - if (**endptr != '#' - || (*nptr < '1' || *nptr > '9') - || (n < 2 || n > 64) - ) { - return n; - } + arith_t n = 0; + const char *start = nptr; - /* It's "N#nnnn" or "NN#nnnn" syntax, NN can't start with 0, - * NN is in 2..64 range. - */ - base = (unsigned)n; - n = 0; - nptr = *endptr + 1; for (;;) { unsigned digit = (unsigned)*nptr - '0'; if (digit >= 10 /* not 0..9 */ @@ -582,15 +564,52 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) n = n * base + digit; nptr++; } - /* Note: we do not set errno on bad chars, we just set a pointer - * to the first invalid char. For example, this allows - * "N#" (empty "nnnn" part): 64#+1 is a valid expression, - * it means 64# + 1, whereas 64#~... is not, since ~ is not a valid - * operator. - */ *endptr = (char*)nptr; + /* "64#" and "64#+1" used to be valid expressions, but bash 5.2.15 + * no longer allow such, detect this: + */ +// NB: bash allows $((0x)), this is probably a bug... + if (nptr == start) + *endptr = NULL; /* there weren't any digits, bad */ return n; } + +static arith_t strto_arith_t(const char *nptr, char **endptr) +{ +/* NB: we do not use strtoull here to be bash-compatible: + * $((99999999999999999999)) is 7766279631452241919 + * (the 64-bit truncated value). + */ + unsigned base; + + /* nptr[0] is '0'..'9' here */ + + base = nptr[0] - '0'; + if (base == 0) { /* nptr[0] is '0' */ + base = 8; + if ((nptr[1] | 0x20) == 'x') { + base = 16; + nptr += 2; + } +// NB: bash allows $((0x)), this is probably a bug... + return parse_with_base(nptr, endptr, base); + } + + if (nptr[1] == '#') { + if (base > 1) + return parse_with_base(nptr + 2, endptr, base); + /* else: bash says "invalid arithmetic base" */ + } + + if (isdigit(nptr[1]) && nptr[2] == '#') { + base = 10 * base + (nptr[1] - '0'); + if (base >= 2 && base <= 64) + return parse_with_base(nptr + 3, endptr, base); + /* else: bash says "invalid arithmetic base" */ + } + + return parse_with_base(nptr, endptr, 10); +} #else /* !ENABLE_FEATURE_SH_MATH_BASE */ # if ENABLE_FEATURE_SH_MATH_64 # define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0) @@ -702,7 +721,6 @@ evaluate_string(arith_state_t *math_state, const char *expr) dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); expr = p; numstackptr->var_name = NULL; - push_num0: numstackptr->val = 0; } push_num: @@ -715,11 +733,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) /* Number */ char *end; numstackptr->var_name = NULL; - errno = 0; /* code is smaller compared to using &expr here: */ numstackptr->val = strto_arith_t(expr, &end); expr = end; dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); + if (!expr) /* example: $((10#)) */ + goto syntax_err; /* A number can't be followed by another number, or a variable name. * We'd catch this later anyway, but this would require numstack[] * to be ~twice as deep to handle strings where _every_ char is @@ -728,13 +747,6 @@ evaluate_string(arith_state_t *math_state, const char *expr) */ if (isalnum(*expr) || *expr == '_') goto syntax_err; - if (errno) { -// TODO: bash 5.2.15 does not catch ERANGE (some older version did?). -// $((99999999999999999999)) is 7766279631452241919 (the 64-bit truncated value). -// Our BASE# code does this as well: try $((10#99999999999999999999)), -// but the "ordinary" code path with strtoull() does not do this. - goto push_num0; /* bash compat */ - } goto push_num; } From vda.linux at googlemail.com Mon Jun 26 08:02:54 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 26 Jun 2023 10:02:54 +0200 Subject: [git commit] shell/math: rename TOK_NUM to TOK_VALUE, improve comments Message-ID: <20230626080328.1842F8431A@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=5d8f8570c0c4220bfadb6d24d4fbc3e722d44802 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/shell/math.c b/shell/math.c index fbf5c587e..0426e2daa 100644 --- a/shell/math.c +++ b/shell/math.c @@ -94,7 +94,6 @@ * * Merge in Aaron's comments previously posted to the busybox list, * modified slightly to take account of my changes to the code. - * */ /* * (C) 2003 Vladimir Oleynik @@ -212,18 +211,18 @@ typedef unsigned char operator; #define TOK_UPLUS tok_decl(UNARYPREC+1,1) #define PREC_PRE (UNARYPREC+2) - #define TOK_PRE_INC tok_decl(PREC_PRE, 0) #define TOK_PRE_DEC tok_decl(PREC_PRE, 1) #define PREC_POST (UNARYPREC+3) - #define TOK_POST_INC tok_decl(PREC_POST, 0) #define TOK_POST_DEC tok_decl(PREC_POST, 1) -#define SPEC_PREC (UNARYPREC+4) - -#define TOK_NUM tok_decl(SPEC_PREC, 0) +/* TOK_VALUE marks a number, name, name++/name--, or (EXPR): + * IOW: something which can be used as the left side of a binary op. + * Since it's never pushed to opstack, its precedence does not matter. + */ +#define TOK_VALUE tok_decl(PREC_POST, 2) static int is_assign_op(operator op) @@ -625,7 +624,9 @@ evaluate_string(arith_state_t *math_state, const char *expr) var_or_num_t *numstack, *numstackptr; /* Stack of operator tokens */ operator *opstack, *opstackptr; + /* To detect whether we are after a "value": */ operator lasttok; + /* To insert implicit () in ?: ternary op: */ operator insert_op = 0xff; unsigned ternary_level = 0; const char *errmsg; @@ -720,12 +721,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) } else { dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); expr = p; - numstackptr->var_name = NULL; - numstackptr->val = 0; + numstackptr->var_name = NULL; /* not needed, paranoia */ + numstackptr->val = 0; /* not needed, paranoia */ } - push_num: + push_value: numstackptr++; - lasttok = TOK_NUM; + lasttok = TOK_VALUE; continue; } @@ -747,7 +748,7 @@ evaluate_string(arith_state_t *math_state, const char *expr) */ if (isalnum(*expr) || *expr == '_') goto syntax_err; - goto push_num; + goto push_value; } /* Should be an operator */ @@ -818,15 +819,14 @@ evaluate_string(arith_state_t *math_state, const char *expr) dbg("insert_op=%02x op=%02x", insert_op, op); } tok_found1: - /* post grammar: a++ reduce to num */ - if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) - lasttok = TOK_NUM; + /* NAME++ is a "value" (something suitable for a binop) */ + if (PREC(lasttok) == PREC_POST) + lasttok = TOK_VALUE; /* Plus and minus are binary (not unary) _only_ if the last - * token was a number, or a right paren (which pretends to be - * a number, since it evaluates to one). Think about it. - * It makes sense. */ - if (lasttok != TOK_NUM) { + * token was a "value". Think about it. It makes sense. + */ + if (lasttok != TOK_VALUE) { switch (op) { case TOK_ADD: //op = TOK_UPLUS; @@ -857,8 +857,8 @@ evaluate_string(arith_state_t *math_state, const char *expr) prec = PREC(op); if (prec != PREC_LPAREN && prec < UNARYPREC) { /* binary, ternary or RPAREN */ - if (lasttok != TOK_NUM) { - /* must be preceded by a num */ + if (lasttok != TOK_VALUE) { + /* must be preceded by a num (example?) */ goto syntax_err; } /* if op is RPAREN: @@ -883,11 +883,12 @@ evaluate_string(arith_state_t *math_state, const char *expr) operator prev_op = *--opstackptr; if (op == TOK_RPAREN) { if (prev_op == TOK_LPAREN) { - /* Erase var name: (var) is just a number, for example, (var) = 1 is not valid */ + /* Erase var name: for example, (VAR) = 1 is not valid */ numstackptr[-1].var_name = NULL; - /* Any operator directly after a - * close paren should consider itself binary */ - lasttok = TOK_NUM; + /* (EXPR) is a "value": next operator directly after + * close paren should be considered binary + */ + lasttok = TOK_VALUE; goto next; } /* Not (y), but ...x~y). Fall through to evaluate x~y */ From vda.linux at googlemail.com Mon Jun 26 09:08:40 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Mon, 26 Jun 2023 11:08:40 +0200 Subject: [git commit] shell/math: improve comments Message-ID: <20230626090853.40F1E8438E@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=e619a25a551ac6a5f215005166371074a9e2816f branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master Signed-off-by: Denys Vlasenko --- shell/math.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/math.c b/shell/math.c index 0426e2daa..9e81d604c 100644 --- a/shell/math.c +++ b/shell/math.c @@ -856,9 +856,11 @@ evaluate_string(arith_state_t *math_state, const char *expr) */ prec = PREC(op); if (prec != PREC_LPAREN && prec < UNARYPREC) { - /* binary, ternary or RPAREN */ + /* Binary, ternary or RPAREN */ if (lasttok != TOK_VALUE) { - /* must be preceded by a num (example?) */ + /* Must be preceded by a value. + * $((2 2 + * 3)) would be accepted without this. + */ goto syntax_err; } /* if op is RPAREN: From bugzilla at busybox.net Mon Jun 26 12:03:53 2023 From: bugzilla at busybox.net (bugzilla at busybox.net) Date: Mon, 26 Jun 2023 12:03:53 +0000 Subject: [Bug 15670] New: modprobe applet doesn't work properly Message-ID: https://bugs.busybox.net/show_bug.cgi?id=15670 Bug ID: 15670 Summary: modprobe applet doesn't work properly Product: Busybox Version: 1.35.x Hardware: All OS: Linux Status: NEW Severity: major Priority: P5 Component: Other Assignee: unassigned at busybox.net Reporter: icastell at circontrol.com CC: busybox-cvs at busybox.net Target Milestone: --- Created attachment 9616 --> https://bugs.busybox.net/attachment.cgi?id=9616&action=edit config file We use "modprobe" applet to load kernel modules. $ ls -l /sbin/modprobe lrwxrwxrwx 1 root root 14 Jun 26 11:00 /sbin/modprobe -> ../bin/busybox $ uname -r 4.9.88 $ find /lib/modules/4.9.88/ | grep extra /lib/modules/4.9.88/extra /lib/modules/4.9.88/extra/lcd.ko /lib/modules/4.9.88/extra/pot.ko $ cat /lib/modules/4.9.88/modules.dep | grep extra extra/lcd.ko: extra/pot.ko: Both kernel drivers load PERFECT using modprobe applet of busybox 1.27.2: $ /bin/busybox BusyBox v1.27.2 (2022-02-02 15:18:49 CET) multi-call binary. $ /bin/busybox modprobe lcd $ /bin/busybox modprobe pot $ lsmod Module Size Used by Tainted: G lcd 4141 0 pot 1930 0 But when using 1.35, 1.36 and 1.36.1 versions (not tested with other versions), only "pot" is loading, but not "lcd": $ /bin/busybox BusyBox v1.36.1 (2023-06-26 11:42:55 UTC) multi-call binary. $ /bin/busybox modprobe pot $ /bin/busybox modprobe lcd $ lsmod Module Size Used by Tainted: G pot 1930 0 Retcode is 0 in all cases: $ /bin/busybox modprobe lcd $ echo $? 0 In all cases, the "lcd" driver can be properly loaded using insmod: $ ls -l /sbin/insmod lrwxrwxrwx 1 root root 14 Jun 26 11:00 /sbin/insmod -> ../bin/busybox $ insmod /lib/modules/4.9.88/extra/lcd.ko $ lsmod Module Size Used by Tainted: G lcd 4141 0 pot 1930 0 If you need more information, please let me know. Thank you! -- You are receiving this mail because: You are on the CC list for the bug. From vda.linux at googlemail.com Wed Jun 28 12:18:35 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Wed, 28 Jun 2023 14:18:35 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230628121855.C3B2885C1A@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=800207b90a4f5f78cbe65a0f4b3ecd3b93abbd7d branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1015 1023 +8 evaluate_string 1309 1295 -14 .rodata 105344 105321 -23 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/2 up/down: 8/-37) Total: -29 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/shell/math.c b/shell/math.c index 9e81d604c..e15b014aa 100644 --- a/shell/math.c +++ b/shell/math.c @@ -141,6 +141,7 @@ typedef unsigned char operator; * but there are 11 of them, which doesn't fit into 3 bits for unique id. * Abusing another precedence level: */ +#define PREC_ASSIGN1 2 #define TOK_ASSIGN tok_decl(2,0) #define TOK_AND_ASSIGN tok_decl(2,1) #define TOK_OR_ASSIGN tok_decl(2,2) @@ -150,11 +151,12 @@ typedef unsigned char operator; #define TOK_LSHIFT_ASSIGN tok_decl(2,6) #define TOK_RSHIFT_ASSIGN tok_decl(2,7) +#define PREC_ASSIGN2 3 #define TOK_MUL_ASSIGN tok_decl(3,0) #define TOK_DIV_ASSIGN tok_decl(3,1) #define TOK_REM_ASSIGN tok_decl(3,2) -#define fix_assignment_prec(prec) do { if (prec == 3) prec = 2; } while (0) +#define fix_assignment_prec(prec) do { prec -= (prec == 3); } while (0) /* Ternary conditional operator is right associative too */ /* @@ -211,25 +213,25 @@ typedef unsigned char operator; #define TOK_UPLUS tok_decl(UNARYPREC+1,1) #define PREC_PRE (UNARYPREC+2) -#define TOK_PRE_INC tok_decl(PREC_PRE, 0) -#define TOK_PRE_DEC tok_decl(PREC_PRE, 1) +#define TOK_PRE_INC tok_decl(PREC_PRE,0) +#define TOK_PRE_DEC tok_decl(PREC_PRE,1) #define PREC_POST (UNARYPREC+3) -#define TOK_POST_INC tok_decl(PREC_POST, 0) -#define TOK_POST_DEC tok_decl(PREC_POST, 1) +#define TOK_POST_INC tok_decl(PREC_POST,0) +#define TOK_POST_DEC tok_decl(PREC_POST,1) /* TOK_VALUE marks a number, name, name++/name--, or (EXPR): * IOW: something which can be used as the left side of a binary op. * Since it's never pushed to opstack, its precedence does not matter. */ -#define TOK_VALUE tok_decl(PREC_POST, 2) +#define TOK_VALUE tok_decl(PREC_POST,2) static int is_assign_op(operator op) { operator prec = PREC(op); - fix_assignment_prec(prec); - return prec == PREC(TOK_ASSIGN) + return prec == PREC_ASSIGN1 + || prec == PREC_ASSIGN2 || prec == PREC_PRE || prec == PREC_POST; } From vda.linux at googlemail.com Thu Jun 29 09:01:50 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Thu, 29 Jun 2023 11:01:50 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230630171725.9D89C85E0C@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=6a0ba673820cb6880e2f93f739de7d16d45d2b26 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 1023 996 -27 Signed-off-by: Denys Vlasenko --- shell/math.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/shell/math.c b/shell/math.c index e15b014aa..5b996703e 100644 --- a/shell/math.c +++ b/shell/math.c @@ -313,8 +313,10 @@ arith_lookup_val(arith_state_t *math_state, const char *name, char *endname) } /* "Applying" a token means performing it on the top elements on the integer - * stack. For an unary operator it will only change the top element, but a - * binary operator will pop two arguments and push the result */ + * stack. For an unary operator it will only change the top element, + * a binary operator will pop two arguments and push the result, + * the ternary ?: op will pop three arguments and push the result. + */ static NOINLINE const char* arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_or_num_t **numstackptr) { @@ -337,9 +339,9 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ return "malformed ?: operator"; if (expr1->val != 0) /* select expr2 or expr3 */ top_of_stack--; - expr1->val = top_of_stack->val; - expr1->var_name = NULL; - return NULL; + rez = top_of_stack->val; + top_of_stack = expr1; + goto ret_rez; } if (op == TOK_CONDITIONAL) /* Example: $((a ? b)) */ return "malformed ?: operator"; @@ -469,13 +471,13 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ math_state->setvar(top_of_stack->var_name, buf); *e = c; } - /* After saving, make previous value for v++ or v-- */ - if (op == TOK_POST_INC) - rez--; - if (op == TOK_POST_DEC) - rez++; + /* VAR++ or VAR--? */ + if (PREC(op) == PREC_POST) { + /* Do not store new value to stack (keep old value) */ + goto ret_NULL; + } } - + ret_rez: top_of_stack->val = rez; ret_NULL: /* Erase var name, it is just a number now */ From vda.linux at googlemail.com Fri Jun 30 17:16:41 2023 From: vda.linux at googlemail.com (Denys Vlasenko) Date: Fri, 30 Jun 2023 19:16:41 +0200 Subject: [git commit] shell/math: code shrink Message-ID: <20230630171725.A8D4F85E0F@busybox.osuosl.org> commit: https://git.busybox.net/busybox/commit/?id=38f769ab4e5f6cd2ffab88300ddaddef3aac3345 branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master function old new delta arith_apply 999 996 -3 evaluate_string 1295 1291 -4 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-7) Total: -7 bytes Signed-off-by: Denys Vlasenko --- shell/math.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shell/math.c b/shell/math.c index 5b996703e..beb89d140 100644 --- a/shell/math.c +++ b/shell/math.c @@ -369,7 +369,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (math_state->evaluation_disabled) { dbg("binary op %02x skipped", op); - goto ret_NULL; + return NULL; /* bash 5.2.12 does not execute "2/0" in disabled * branches of ?: (and thus does not complain), * but complains about negative exp: "2**-1". @@ -452,7 +452,7 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_ if (math_state->evaluation_disabled) { dbg("unary op %02x skipped", op); - goto ret_NULL; + return NULL; } if (is_assign_op(op)) { @@ -598,15 +598,18 @@ static arith_t strto_arith_t(const char *nptr, char **endptr) return parse_with_base(nptr, endptr, base); } + /* base is 1..9 here */ + if (nptr[1] == '#') { if (base > 1) return parse_with_base(nptr + 2, endptr, base); - /* else: bash says "invalid arithmetic base" */ + /* else: "1#NN", bash says "invalid arithmetic base" */ } if (isdigit(nptr[1]) && nptr[2] == '#') { base = 10 * base + (nptr[1] - '0'); - if (base >= 2 && base <= 64) + /* base is at least 10 here */ + if (base <= 64) return parse_with_base(nptr + 3, endptr, base); /* else: bash says "invalid arithmetic base" */ }