From f56ddf2e4ce42d7914d048f42fd952302027e8a3 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 27 Jun 2017 17:51:07 +0200
Subject: ash: fix $HOME/.profile reading if !ASH_EXPAND_PRMT

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 6 ------
 1 file changed, 6 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index eb51d47cc..9681111cc 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -2458,12 +2458,8 @@ putprompt(const char *s)
 }
 #endif
 
-#if ENABLE_ASH_EXPAND_PRMT
 /* expandstr() needs parsing machinery, so it is far away ahead... */
 static const char *expandstr(const char *ps);
-#else
-#define expandstr(s) s
-#endif
 
 static void
 setprompt_if(smallint do_set, int whichprompt)
@@ -12449,7 +12445,6 @@ parseheredoc(void)
 /*
  * called by editline -- any expansions to the prompt should be added here.
  */
-#if ENABLE_ASH_EXPAND_PRMT
 static const char *
 expandstr(const char *ps)
 {
@@ -12475,7 +12470,6 @@ expandstr(const char *ps)
 	expandarg(&n, NULL, EXP_QUOTED);
 	return stackblock();
 }
-#endif
 
 /*
  * Execute a command or commands contained in a string.
-- 
cgit v1.2.3-55-g6feb


From e9aba3e7ea4936278188555332654796e5b5e873 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sat, 1 Jul 2017 21:09:27 +0200
Subject: ash: fix 'trap - 65'

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 9681111cc..22c726043 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -12967,7 +12967,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 	exitcode = 0;
 	while (*ap) {
 		signo = get_signum(*ap);
-		if (signo < 0) {
+		if (signo < 0 || signo >= NSIG) {
 			/* Mimic bash message exactly */
 			ash_msg("%s: invalid signal specification", *ap);
 			exitcode = 1;
-- 
cgit v1.2.3-55-g6feb


From 48c803a2064d5ae24540760f13a21f092247bd82 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sat, 1 Jul 2017 23:24:48 +0200
Subject: ash: fix $HOME/.profile reading if !ASH_EXPAND_PRMT, take 2

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 22c726043..6d46e3719 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -2484,10 +2484,10 @@ setprompt_if(smallint do_set, int whichprompt)
 	}
 #if ENABLE_ASH_EXPAND_PRMT
 	pushstackmark(&smark, stackblocksize());
-#endif
 	putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
 	popstackmark(&smark);
+#else
+	putprompt(prompt);
 #endif
 }
 
@@ -11534,9 +11534,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 	smallint dblquote;
 	smallint oldstyle;
 	IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
-#if ENABLE_ASH_EXPAND_PRMT
 	smallint pssyntax;   /* we are expanding a prompt string */
-#endif
 	int varnest;         /* levels of variables expansion */
 	IF_FEATURE_SH_MATH(int arinest;)    /* levels of arithmetic expansion */
 	IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
@@ -11548,11 +11546,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 	bqlist = NULL;
 	quotef = 0;
 	IF_FEATURE_SH_MATH(prevsyntax = 0;)
-#if ENABLE_ASH_EXPAND_PRMT
 	pssyntax = (syntax == PSSYNTAX);
 	if (pssyntax)
 		syntax = DQSYNTAX;
-#endif
 	dblquote = (syntax == DQSYNTAX);
 	varnest = 0;
 	IF_FEATURE_SH_MATH(arinest = 0;)
@@ -11606,12 +11602,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 			} else if (c == '\n') {
 				nlprompt();
 			} else {
-#if ENABLE_ASH_EXPAND_PRMT
 				if (c == '$' && pssyntax) {
 					USTPUTC(CTLESC, out);
 					USTPUTC('\\', out);
 				}
-#endif
 				/* Backslash is retained if we are in "str" and next char isn't special */
 				if (dblquote
 				 && c != '\\'
-- 
cgit v1.2.3-55-g6feb


From 4ee824f6ba3f35228f1c48e21681aa532a7dc23f Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 3 Jul 2017 01:22:13 +0200
Subject: randomconfig fixes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 networking/udhcp/common.h | 2 ++
 networking/udhcp/packet.c | 2 ++
 shell/hush.c              | 8 +++++---
 3 files changed, 9 insertions(+), 3 deletions(-)

(limited to 'shell')

diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index 6907e7f60..a9c23a186 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -295,7 +295,9 @@ int FAST_FUNC udhcp_str2optset(const char *str,
 		const struct dhcp_optflag *optflags,
 		const char *option_strings);
 
+#if ENABLE_UDHCPC || ENABLE_UDHCPD
 void udhcp_init_header(struct dhcp_packet *packet, char type) FAST_FUNC;
+#endif
 
 int udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd) FAST_FUNC;
 
diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c
index 0a31f2643..9e1b46d2f 100644
--- a/networking/udhcp/packet.c
+++ b/networking/udhcp/packet.c
@@ -12,6 +12,7 @@
 #include <netinet/if_ether.h>
 #include <netpacket/packet.h>
 
+#if ENABLE_UDHCPC || ENABLE_UDHCPD
 void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type)
 {
 	memset(packet, 0, sizeof(*packet));
@@ -29,6 +30,7 @@ void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type)
 		packet->options[0] = DHCP_END;
 	udhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type);
 }
+#endif
 
 #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
 void FAST_FUNC udhcp_dump_packet(struct dhcp_packet *packet)
diff --git a/shell/hush.c b/shell/hush.c
index 125463a56..f5c1e5bc1 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -331,9 +331,9 @@
 /* Separate defines document which part of code implements what */
 #define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT
 #define BASH_SUBSTR        ENABLE_HUSH_BASH_COMPAT
-#define BASH_TEST2         ENABLE_HUSH_BASH_COMPAT
 #define BASH_SOURCE        ENABLE_HUSH_BASH_COMPAT
 #define BASH_HOSTNAME_VAR  ENABLE_HUSH_BASH_COMPAT
+#define BASH_TEST2         (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
 
 
 /* Build knobs */
@@ -1457,7 +1457,7 @@ static void restore_redirected_FILEs(void)
 		fl = fl->next;
 	}
 }
-#if ENABLE_FEATURE_SH_STANDALONE
+#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
 static void close_all_FILE_list(void)
 {
 	struct FILE_list *fl = G.FILE_list;
@@ -8575,8 +8575,9 @@ int hush_main(int argc, char **argv)
 			optarg++;
 			empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
 			if (empty_trap_mask != 0) {
-				int sig;
+				IF_HUSH_TRAP(int sig;)
 				install_special_sighandlers();
+# if ENABLE_HUSH_TRAP
 				G_traps = xzalloc(sizeof(G_traps[0]) * NSIG);
 				for (sig = 1; sig < NSIG; sig++) {
 					if (empty_trap_mask & (1LL << sig)) {
@@ -8584,6 +8585,7 @@ int hush_main(int argc, char **argv)
 						install_sighandler(sig, SIG_IGN);
 					}
 				}
+# endif
 			}
 # if ENABLE_HUSH_LOOPS
 			optarg++;
-- 
cgit v1.2.3-55-g6feb


From 2e989ef232e35750df573898077dd356003705b2 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 3 Jul 2017 16:56:37 +0200
Subject: msh: delete this applet

It's deprecated since 2009 and interferes with make_single_applets.sh tests.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 configs/TEST_nommu_defconfig                           |  1 -
 configs/TEST_noprintf_defconfig                        |  1 -
 configs/TEST_rh9_defconfig                             |  1 -
 configs/android2_defconfig                             |  1 -
 configs/android_502_defconfig                          |  1 -
 configs/android_defconfig                              |  1 -
 configs/android_ndk_defconfig                          |  1 -
 configs/cygwin_defconfig                               |  1 -
 configs/freebsd_defconfig                              |  1 -
 shell/ash_test/run-all                                 |  6 +++---
 shell/hush.c                                           | 18 ------------------
 .../msh/msh-supports-underscores-in-variable-names     |  1 -
 12 files changed, 3 insertions(+), 31 deletions(-)
 delete mode 100644 testsuite/msh/msh-supports-underscores-in-variable-names

(limited to 'shell')

diff --git a/configs/TEST_nommu_defconfig b/configs/TEST_nommu_defconfig
index 7fbbbecc7..6ff68a092 100644
--- a/configs/TEST_nommu_defconfig
+++ b/configs/TEST_nommu_defconfig
@@ -891,7 +891,6 @@ CONFIG_HUSH_FUNCTIONS=y
 CONFIG_HUSH_LOCAL=y
 CONFIG_HUSH_EXPORT_N=y
 CONFIG_HUSH_RANDOM_SUPPORT=y
-CONFIG_MSH=y
 CONFIG_SH_MATH_SUPPORT=y
 CONFIG_SH_MATH_SUPPORT_64=y
 CONFIG_FEATURE_SH_EXTRA_QUIET=y
diff --git a/configs/TEST_noprintf_defconfig b/configs/TEST_noprintf_defconfig
index 3f85ee1df..4b2ef402a 100644
--- a/configs/TEST_noprintf_defconfig
+++ b/configs/TEST_noprintf_defconfig
@@ -898,7 +898,6 @@ CONFIG_FEATURE_SH_IS_NONE=y
 # CONFIG_FEATURE_BASH_IS_ASH is not set
 # CONFIG_FEATURE_BASH_IS_HUSH is not set
 CONFIG_FEATURE_BASH_IS_NONE=y
-# CONFIG_MSH is not set
 # CONFIG_SH_MATH_SUPPORT is not set
 # CONFIG_SH_MATH_SUPPORT_64 is not set
 # CONFIG_FEATURE_SH_EXTRA_QUIET is not set
diff --git a/configs/TEST_rh9_defconfig b/configs/TEST_rh9_defconfig
index 34d8e31e2..52f3e4670 100644
--- a/configs/TEST_rh9_defconfig
+++ b/configs/TEST_rh9_defconfig
@@ -905,7 +905,6 @@ CONFIG_HUSH_FUNCTIONS=y
 CONFIG_HUSH_LOCAL=y
 CONFIG_HUSH_EXPORT_N=y
 CONFIG_HUSH_RANDOM_SUPPORT=y
-CONFIG_MSH=y
 CONFIG_SH_MATH_SUPPORT=y
 CONFIG_SH_MATH_SUPPORT_64=y
 CONFIG_FEATURE_SH_EXTRA_QUIET=y
diff --git a/configs/android2_defconfig b/configs/android2_defconfig
index 20866c32b..9202320a4 100644
--- a/configs/android2_defconfig
+++ b/configs/android2_defconfig
@@ -952,7 +952,6 @@ CONFIG_CTTYHACK=y
 # CONFIG_HUSH_RANDOM_SUPPORT is not set
 # CONFIG_HUSH_EXPORT_N is not set
 # CONFIG_HUSH_MODE_X is not set
-# CONFIG_MSH is not set
 # CONFIG_FEATURE_SH_IS_ASH is not set
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 CONFIG_FEATURE_SH_IS_NONE=y
diff --git a/configs/android_502_defconfig b/configs/android_502_defconfig
index bdca9eebb..1901bdbb0 100644
--- a/configs/android_502_defconfig
+++ b/configs/android_502_defconfig
@@ -1098,7 +1098,6 @@ CONFIG_CTTYHACK=y
 # CONFIG_HUSH_RANDOM_SUPPORT is not set
 # CONFIG_HUSH_EXPORT_N is not set
 # CONFIG_HUSH_MODE_X is not set
-# CONFIG_MSH is not set
 CONFIG_FEATURE_SH_IS_ASH=y
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 # CONFIG_FEATURE_SH_IS_NONE is not set
diff --git a/configs/android_defconfig b/configs/android_defconfig
index 6ef81750e..ea6e8a79e 100644
--- a/configs/android_defconfig
+++ b/configs/android_defconfig
@@ -984,7 +984,6 @@ CONFIG_CTTYHACK=y
 # CONFIG_HUSH_RANDOM_SUPPORT is not set
 # CONFIG_HUSH_EXPORT_N is not set
 # CONFIG_HUSH_MODE_X is not set
-# CONFIG_MSH is not set
 # CONFIG_FEATURE_SH_IS_ASH is not set
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 CONFIG_FEATURE_SH_IS_NONE=y
diff --git a/configs/android_ndk_defconfig b/configs/android_ndk_defconfig
index 35d03b42e..61871fcb1 100644
--- a/configs/android_ndk_defconfig
+++ b/configs/android_ndk_defconfig
@@ -1013,7 +1013,6 @@ CONFIG_CTTYHACK=y
 # CONFIG_HUSH_RANDOM_SUPPORT is not set
 # CONFIG_HUSH_EXPORT_N is not set
 # CONFIG_HUSH_MODE_X is not set
-# CONFIG_MSH is not set
 # CONFIG_FEATURE_SH_IS_ASH is not set
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 CONFIG_FEATURE_SH_IS_NONE=y
diff --git a/configs/cygwin_defconfig b/configs/cygwin_defconfig
index 6bfc973ef..54aa44470 100644
--- a/configs/cygwin_defconfig
+++ b/configs/cygwin_defconfig
@@ -955,7 +955,6 @@ CONFIG_HUSH_LOCAL=y
 CONFIG_HUSH_RANDOM_SUPPORT=y
 CONFIG_HUSH_EXPORT_N=y
 CONFIG_HUSH_MODE_X=y
-# CONFIG_MSH is not set
 CONFIG_FEATURE_SH_IS_ASH=y
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 # CONFIG_FEATURE_SH_IS_NONE is not set
diff --git a/configs/freebsd_defconfig b/configs/freebsd_defconfig
index e3d04aedc..fadbca13b 100644
--- a/configs/freebsd_defconfig
+++ b/configs/freebsd_defconfig
@@ -931,7 +931,6 @@ CONFIG_ASH=y
 # CONFIG_HUSH_RANDOM_SUPPORT is not set
 # CONFIG_HUSH_EXPORT_N is not set
 # CONFIG_HUSH_MODE_X is not set
-# CONFIG_MSH is not set
 CONFIG_FEATURE_SH_IS_ASH=y
 # CONFIG_FEATURE_SH_IS_HUSH is not set
 # CONFIG_FEATURE_SH_IS_NONE is not set
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 354cc1fcf..8dfdddd9f 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -59,9 +59,9 @@ do_test()
 if [ $# -lt 1 ]; then
     # All sub directories
     modules=`ls -d ash-*`
-    # If you want to test ash against hush and msh testsuites
-    # (have to copy hush_test and msh_test dirs to current dir first):
-    #modules=`ls -d ash-* hush_test/hush-* msh_test/msh-*`
+    # If you want to test ash against hush testsuite
+    # (have to copy hush_test dir to current dir first):
+    #modules=`ls -d ash-* hush_test/hush-*`
 
     for module in $modules; do
 	do_test $module
diff --git a/shell/hush.c b/shell/hush.c
index f5c1e5bc1..fc6db316e 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -268,17 +268,9 @@
 //config:	bool "memleak builtin (debugging)"
 //config:	default n
 //config:	depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
-//config:
-//config:config MSH
-//config:	bool "msh (deprecated: aliased to hush)"
-//config:	default n
-//config:	select HUSH
-//config:	help
-//config:	  msh is deprecated and will be removed, please migrate to hush.
 
 //applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
 //                       APPLET_ODDNAME:name  main  location    suid_type     help
-//applet:IF_MSH(         APPLET_ODDNAME(msh,  hush, BB_DIR_BIN, BB_SUID_DROP, hush))
 //applet:IF_SH_IS_HUSH(  APPLET_ODDNAME(sh,   hush, BB_DIR_BIN, BB_SUID_DROP, hush))
 //applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
 
@@ -8808,16 +8800,6 @@ int hush_main(int argc, char **argv)
 }
 
 
-#if ENABLE_MSH
-int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int msh_main(int argc, char **argv)
-{
-	bb_error_msg("msh is deprecated, please use hush instead");
-	return hush_main(argc, argv);
-}
-#endif
-
-
 /*
  * Built-ins
  */
diff --git a/testsuite/msh/msh-supports-underscores-in-variable-names b/testsuite/msh/msh-supports-underscores-in-variable-names
deleted file mode 100644
index 9c7834b37..000000000
--- a/testsuite/msh/msh-supports-underscores-in-variable-names
+++ /dev/null
@@ -1 +0,0 @@
-test "`busybox msh -c 'FOO_BAR=foo; echo $FOO_BAR'`" = foo
-- 
cgit v1.2.3-55-g6feb


From d4e4fdb5ce5ccc067b3d35d877f7a7d978869517 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 3 Jul 2017 21:31:16 +0200
Subject: fixes for bugs found by make_single_applets.sh

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 archival/bbunzip.c         | 11 ++++++++---
 include/libbb.h            |  2 ++
 libbb/appletlib.c          | 20 ++++++++++++++++----
 libbb/getopt32.c           |  4 +---
 libbb/vfork_daemon_rexec.c |  3 ++-
 networking/inetd.c         | 17 ++++++++++-------
 scripts/randomtest.loop    | 26 ++++++++++++++++++++++++++
 shell/hush.c               | 26 ++++++--------------------
 shell/shell_common.c       |  4 +---
 util-linux/fdisk.c         |  3 +++
 10 files changed, 75 insertions(+), 41 deletions(-)

(limited to 'shell')

diff --git a/archival/bbunzip.c b/archival/bbunzip.c
index c60f6e6df..f7a7ab354 100644
--- a/archival/bbunzip.c
+++ b/archival/bbunzip.c
@@ -7,14 +7,19 @@
 #include "libbb.h"
 #include "bb_archive.h"
 
+//kbuild:lib-$(CONFIG_ZCAT) += bbunzip.o
+//kbuild:lib-$(CONFIG_GUNZIP) += bbunzip.o
+//kbuild:lib-$(CONFIG_BZCAT) += bbunzip.o
+//kbuild:lib-$(CONFIG_BUNZIP2) += bbunzip.o
+
 /* lzop_main() uses bbunpack(), need this: */
 //kbuild:lib-$(CONFIG_LZOP) += bbunzip.o
 //kbuild:lib-$(CONFIG_LZOPCAT) += bbunzip.o
 //kbuild:lib-$(CONFIG_UNLZOP) += bbunzip.o
 /* bzip2_main() too: */
-//kbuild:lib-$(CONFIG_FEATURE_BZIP2_DECOMPRESS) += bbunzip.o
+//kbuild:lib-$(CONFIG_BZIP2) += bbunzip.o
 /* gzip_main() too: */
-//kbuild:lib-$(CONFIG_FEATURE_GZIP_DECOMPRESS) += bbunzip.o
+//kbuild:lib-$(CONFIG_GZIP) += bbunzip.o
 
 /* Note: must be kept in sync with archival/lzop.c */
 enum {
@@ -443,7 +448,7 @@ int gunzip_main(int argc UNUSED_PARAM, char **argv)
 //applet:IF_BUNZIP2(APPLET(bunzip2, BB_DIR_USR_BIN, BB_SUID_DROP))
 //                APPLET_ODDNAME:name   main     location        suid_type     help
 //applet:IF_BZCAT(APPLET_ODDNAME(bzcat, bunzip2, BB_DIR_USR_BIN, BB_SUID_DROP, bzcat))
-#if ENABLE_FEATURE_BZIP2_DECOMPRESS
+#if ENABLE_FEATURE_BZIP2_DECOMPRESS || ENABLE_BUNZIP2
 int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int bunzip2_main(int argc UNUSED_PARAM, char **argv)
 {
diff --git a/include/libbb.h b/include/libbb.h
index 9b72c97be..557978e66 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -778,6 +778,8 @@ ssize_t recv_from_to(int fd, void *buf, size_t len, int flags,
 
 uint16_t inet_cksum(uint16_t *addr, int len) FAST_FUNC;
 
+/* 0 if argv[0] is NULL: */
+unsigned string_array_len(char **argv) FAST_FUNC;
 void overlapping_strcpy(char *dst, const char *src) FAST_FUNC;
 char *safe_strncpy(char *dst, const char *src, size_t size) FAST_FUNC;
 char *strncpy_IFNAMSIZ(char *dst, const char *src) FAST_FUNC;
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index 7f0d62060..2dea2b43a 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -78,6 +78,17 @@
 #endif
 
 
+unsigned FAST_FUNC string_array_len(char **argv)
+{
+	char **start = argv;
+
+	while (*argv)
+		argv++;
+
+	return argv - start;
+}
+
+
 #if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
 static const char usage_messages[] ALIGN1 = UNPACKED_USAGE;
 #else
@@ -868,10 +879,7 @@ static int busybox_main(char **argv)
 # if NUM_APPLETS > 0
 void FAST_FUNC run_applet_no_and_exit(int applet_no, char **argv)
 {
-	int argc = 1;
-
-	while (argv[argc])
-		argc++;
+	int argc = string_array_len(argv);
 
 	/* Reinit some shared global data */
 	xfunc_error_retval = EXIT_FAILURE;
@@ -993,7 +1001,11 @@ int main(int argc UNUSED_PARAM, char **argv)
 	}
 	/* applet_names in this case is just "applet\0\0" */
 	lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
+# if ENABLE_BUILD_LIBBUSYBOX
+	return SINGLE_APPLET_MAIN(string_array_len(argv), argv);
+# else
 	return SINGLE_APPLET_MAIN(argc, argv);
+# endif
 
 #elif !ENABLE_BUSYBOX && NUM_APPLETS == 0
 
diff --git a/libbb/getopt32.c b/libbb/getopt32.c
index b87b83538..80f4cc060 100644
--- a/libbb/getopt32.c
+++ b/libbb/getopt32.c
@@ -379,9 +379,7 @@ getopt32(char **argv, const char *applet_opts, ...)
 	int spec_flgs = 0;
 
 	/* skip 0: some applets cheat: they do not actually HAVE argv[0] */
-	argc = 1;
-	while (argv[argc])
-		argc++;
+	argc = 1 + string_array_len(argv + 1);
 
 	va_start(p, applet_opts);
 
diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c
index fd481bf6e..2695f99ee 100644
--- a/libbb/vfork_daemon_rexec.c
+++ b/libbb/vfork_daemon_rexec.c
@@ -16,6 +16,7 @@
  */
 
 #include "busybox.h" /* uses applet tables */
+#include "NUM_APPLETS.h"
 
 /* This does a fork/exec in one call, using vfork().  Returns PID of new child,
  * -1 for failure.  Runs argv[0], searching path if that has no / in it. */
@@ -156,7 +157,7 @@ int FAST_FUNC run_nofork_applet(int applet_no, char **argv)
 int FAST_FUNC spawn_and_wait(char **argv)
 {
 	int rc;
-#if ENABLE_FEATURE_PREFER_APPLETS
+#if ENABLE_FEATURE_PREFER_APPLETS && (NUM_APPLETS > 1)
 	int a = find_applet_by_name(argv[0]);
 
 	if (a >= 0) {
diff --git a/networking/inetd.c b/networking/inetd.c
index 01e659f13..39169a935 100644
--- a/networking/inetd.c
+++ b/networking/inetd.c
@@ -1513,8 +1513,11 @@ int inetd_main(int argc UNUSED_PARAM, char **argv)
 	} /* for (;;) */
 }
 
-#if !BB_MMU
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+# if !BB_MMU
 static const char *const cat_args[] = { "cat", NULL };
+# endif
 #endif
 
 /*
@@ -1525,14 +1528,14 @@ static const char *const cat_args[] = { "cat", NULL };
 /* ARGSUSED */
 static void FAST_FUNC echo_stream(int s, servtab_t *sep UNUSED_PARAM)
 {
-#if BB_MMU
+# if BB_MMU
 	while (1) {
 		ssize_t sz = safe_read(s, line, LINE_SIZE);
 		if (sz <= 0)
 			break;
 		xwrite(s, line, sz);
 	}
-#else
+# else
 	/* We are after vfork here! */
 	/* move network socket to stdin/stdout */
 	xmove_fd(s, STDIN_FILENO);
@@ -1542,7 +1545,7 @@ static void FAST_FUNC echo_stream(int s, servtab_t *sep UNUSED_PARAM)
 	xopen(bb_dev_null, O_WRONLY);
 	BB_EXECVP("cat", (char**)cat_args);
 	/* on failure we return to main, which does exit(EXIT_FAILURE) */
-#endif
+# endif
 }
 static void FAST_FUNC echo_dg(int s, servtab_t *sep)
 {
@@ -1566,10 +1569,10 @@ static void FAST_FUNC echo_dg(int s, servtab_t *sep)
 /* ARGSUSED */
 static void FAST_FUNC discard_stream(int s, servtab_t *sep UNUSED_PARAM)
 {
-#if BB_MMU
+# if BB_MMU
 	while (safe_read(s, line, LINE_SIZE) > 0)
 		continue;
-#else
+# else
 	/* We are after vfork here! */
 	/* move network socket to stdin */
 	xmove_fd(s, STDIN_FILENO);
@@ -1580,7 +1583,7 @@ static void FAST_FUNC discard_stream(int s, servtab_t *sep UNUSED_PARAM)
 	xdup2(STDOUT_FILENO, STDERR_FILENO);
 	BB_EXECVP("cat", (char**)cat_args);
 	/* on failure we return to main, which does exit(EXIT_FAILURE) */
-#endif
+# endif
 }
 /* ARGSUSED */
 static void FAST_FUNC discard_dg(int s, servtab_t *sep UNUSED_PARAM)
diff --git a/scripts/randomtest.loop b/scripts/randomtest.loop
index 710f5fd05..4d14b652f 100755
--- a/scripts/randomtest.loop
+++ b/scripts/randomtest.loop
@@ -1,7 +1,11 @@
 #!/bin/sh
 
+run_testsuite=false
 run_testsuite=true
 
+run_single_test=false
+run_single_test=true
+
 test -d "$1" || { echo "'$1' is not a directory"; exit 1; }
 test -x "$1/scripts/randomtest" || { echo "No scripts/randomtest in '$1'"; exit 1; }
 
@@ -40,6 +44,28 @@ while sleep 1; do
 		fi
 		tail -n10 -- "$dir/testsuite/runtest.log"
 	fi
+	if $run_single_test; then
+		(
+			cd -- "$dir" || exit 1
+			echo "Running make_single_applets.sh in $dir..."
+
+			if grep -q '# CONFIG_FEATURE_TFTP_GET is not set' .config \
+			&& grep -q '# CONFIG_FEATURE_TFTP_PUT is not set' .config \
+			; then
+				# If both off, tftp[d] is ifdefed out and test fails.
+				# Enable one:
+				sed 's/# CONFIG_FEATURE_TFTP_GET is not set/CONFIG_FEATURE_TFTP_GET=y/' -i .config
+			fi
+
+			./make_single_applets.sh
+		)
+		if test $? != 0; then
+			echo "Failed make_single_applets.sh in $dir"
+			exit 1 # you may comment this out...
+			let fail++
+			continue
+		fi
+	fi
 	rm -rf -- "$dir"
 	let cnt++
 done
diff --git a/shell/hush.c b/shell/hush.c
index fc6db316e..30add72f0 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1478,8 +1478,6 @@ typedef struct save_arg_t {
 
 static void save_and_replace_G_args(save_arg_t *sv, char **argv)
 {
-	int n;
-
 	sv->sv_argv0 = argv[0];
 	sv->sv_g_argv = G.global_argv;
 	sv->sv_g_argc = G.global_argc;
@@ -1489,10 +1487,7 @@ static void save_and_replace_G_args(save_arg_t *sv, char **argv)
 	G.global_argv = argv;
 	IF_HUSH_SET(G.global_args_malloced = 0;)
 
-	n = 1;
-	while (*++argv)
-		n++;
-	G.global_argc = n;
+	G.global_argc = 1 + string_array_len(argv + 1);
 }
 
 static void restore_G_args(save_arg_t *sv, char **argv)
@@ -6809,13 +6804,11 @@ static void exec_function(char ***to_free,
 		char **argv)
 {
 # if BB_MMU
-	int n = 1;
+	int n;
 
 	argv[0] = G.global_argv[0];
 	G.global_argv = argv;
-	while (*++argv)
-		n++;
-	G.global_argc = n;
+	G.global_argc = n = 1 + string_array_len(argv + 1);
 	/* On MMU, funcp->body is always non-NULL */
 	n = run_list(funcp->body);
 	fflush_all();
@@ -8811,12 +8804,8 @@ static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM)
 #if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL
 static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
 {
-	int argc = 0;
-	while (*argv) {
-		argc++;
-		argv++;
-	}
-	return applet_main_func(argc, argv - argc);
+	int argc = string_array_len(argv);
+	return applet_main_func(argc, argv);
 }
 #endif
 #if ENABLE_HUSH_TEST || BASH_TEST2
@@ -9363,10 +9352,7 @@ static int FAST_FUNC builtin_set(char **argv)
 	/* This realloc's G.global_argv */
 	G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1);
 
-	n = 1;
-	while (*++pp)
-		n++;
-	G.global_argc = n;
+	G.global_argc = 1 + string_array_len(pp + 1);
 
 	return EXIT_SUCCESS;
 
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 03b7d0b75..bf56f3d78 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -405,9 +405,7 @@ shell_builtin_ulimit(char **argv)
 	 */
 	GETOPT_RESET();
 
-	argc = 1;
-	while (argv[argc])
-		argc++;
+	argc = string_array_len(argv);
 
 	opts = 0;
 	while (1) {
diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c
index 916d4e30e..4467525c7 100644
--- a/util-linux/fdisk.c
+++ b/util-linux/fdisk.c
@@ -185,6 +185,8 @@ struct hd_geometry {
 
 #define HDIO_GETGEO     0x0301  /* get device geometry */
 
+/* TODO: #if ENABLE_FEATURE_FDISK_WRITABLE */
+/* (currently fdisk_sun/sgi.c do not have proper WRITABLE #ifs) */
 static const char msg_building_new_label[] ALIGN1 =
 "Building a new %s. Changes will remain in memory only,\n"
 "until you decide to write them. After that the previous content\n"
@@ -192,6 +194,7 @@ static const char msg_building_new_label[] ALIGN1 =
 
 static const char msg_part_already_defined[] ALIGN1 =
 "Partition %u is already defined, delete it before re-adding\n";
+/* #endif */
 
 
 struct partition {
-- 
cgit v1.2.3-55-g6feb


From fda9fafe279d9394ad53313320a949c86f646734 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 5 Jul 2017 19:10:21 +0200
Subject: ash: fix matching of unicode greek letter rho (cf 81) and similar
 cases

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c                                        | 23 +++++++++++++++++-
 shell/ash_test/ash-quoting/unicode_8x_chars.right  |  6 +++++
 shell/ash_test/ash-quoting/unicode_8x_chars.tests  | 28 ++++++++++++++++++++++
 .../hush_test/hush-quoting/unicode_8x_chars.right  |  6 +++++
 .../hush_test/hush-quoting/unicode_8x_chars.tests  | 28 ++++++++++++++++++++++
 5 files changed, 90 insertions(+), 1 deletion(-)
 create mode 100644 shell/ash_test/ash-quoting/unicode_8x_chars.right
 create mode 100755 shell/ash_test/ash-quoting/unicode_8x_chars.tests
 create mode 100644 shell/hush_test/hush-quoting/unicode_8x_chars.right
 create mode 100755 shell/hush_test/hush-quoting/unicode_8x_chars.tests

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 6d46e3719..e5fdd1646 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -5913,6 +5913,7 @@ rmescapes(char *str, int flag)
 	while (*p) {
 		if ((unsigned char)*p == CTLQUOTEMARK) {
 // Note: both inquotes and protect_against_glob only affect whether
+// CTLESC,<ch> gets converted to <ch> or to \<ch>
 			inquotes = ~inquotes;
 			p++;
 			protect_against_glob = globbing;
@@ -5925,7 +5926,27 @@ rmescapes(char *str, int flag)
 				ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)");
 #endif
 			if (protect_against_glob) {
-				*q++ = '\\';
+				/*
+				 * We used to trust glob() and fnmatch() to eat
+				 * superfluous escapes (\z where z has no
+				 * special meaning anyway). But this causes
+				 * bugs such as string of one greek letter rho
+				 * (unicode-encoded as two bytes 'cf,81")
+				 * getting encoded as "cf,CTLESC,81"
+				 * and here, converted to "cf,\,81" -
+				 * which does not go well with some flavors
+				 * of fnmatch() in unicode locales.
+				 *
+				 * Lets add "\" only on the chars which need it.
+				 */
+				if (*p == '*'
+				 || *p == '?'
+				 || *p == '['
+				/* || *p == ']' maybe also this? */
+				 || *p == '\\'
+				) {
+					*q++ = '\\';
+				}
 			}
 		} else if (*p == '\\' && !inquotes) {
 			/* naked back slash */
diff --git a/shell/ash_test/ash-quoting/unicode_8x_chars.right b/shell/ash_test/ash-quoting/unicode_8x_chars.right
new file mode 100644
index 000000000..7780b88b4
--- /dev/null
+++ b/shell/ash_test/ash-quoting/unicode_8x_chars.right
@@ -0,0 +1,6 @@
+ok
+ok
+ok
+ok
+ok
+ok
diff --git a/shell/ash_test/ash-quoting/unicode_8x_chars.tests b/shell/ash_test/ash-quoting/unicode_8x_chars.tests
new file mode 100755
index 000000000..1258745ec
--- /dev/null
+++ b/shell/ash_test/ash-quoting/unicode_8x_chars.tests
@@ -0,0 +1,28 @@
+# Unicode: cf 80
+case π in
+( "π" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+# Unicode: cf 81
+case ρ in
+( "ρ" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+# Unicode: cf 82
+case ς in
+( "ς" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+
+case "π" in
+( π ) echo ok ;;
+( * ) echo WRONG ;;
+esac
+case "ρ" in
+( ρ ) echo ok ;;
+( * ) echo WRONG ;;
+esac
+case "ς" in
+( ς ) echo ok ;;
+( * ) echo WRONG ;;
+esac
diff --git a/shell/hush_test/hush-quoting/unicode_8x_chars.right b/shell/hush_test/hush-quoting/unicode_8x_chars.right
new file mode 100644
index 000000000..7780b88b4
--- /dev/null
+++ b/shell/hush_test/hush-quoting/unicode_8x_chars.right
@@ -0,0 +1,6 @@
+ok
+ok
+ok
+ok
+ok
+ok
diff --git a/shell/hush_test/hush-quoting/unicode_8x_chars.tests b/shell/hush_test/hush-quoting/unicode_8x_chars.tests
new file mode 100755
index 000000000..1258745ec
--- /dev/null
+++ b/shell/hush_test/hush-quoting/unicode_8x_chars.tests
@@ -0,0 +1,28 @@
+# Unicode: cf 80
+case π in
+( "π" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+# Unicode: cf 81
+case ρ in
+( "ρ" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+# Unicode: cf 82
+case ς in
+( "ς" ) echo ok ;;
+( * )   echo WRONG ;;
+esac
+
+case "π" in
+( π ) echo ok ;;
+( * ) echo WRONG ;;
+esac
+case "ρ" in
+( ρ ) echo ok ;;
+( * ) echo WRONG ;;
+esac
+case "ς" in
+( ς ) echo ok ;;
+( * ) echo WRONG ;;
+esac
-- 
cgit v1.2.3-55-g6feb


From 92b8d9c9faf599cd323c04d4f6853f2de840279c Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 5 Jul 2017 19:13:44 +0200
Subject: ash: note which versions of glibc exhibit "rho bug"

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index e5fdd1646..5da23c34b 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -5935,7 +5935,8 @@ rmescapes(char *str, int flag)
 				 * getting encoded as "cf,CTLESC,81"
 				 * and here, converted to "cf,\,81" -
 				 * which does not go well with some flavors
-				 * of fnmatch() in unicode locales.
+				 * of fnmatch() in unicode locales
+				 * (for example, glibc <= 2.22).
 				 *
 				 * Lets add "\" only on the chars which need it.
 				 */
-- 
cgit v1.2.3-55-g6feb


From ed79a636238ec15c562862787dd71cd9de168b7d Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 5 Jul 2017 19:20:43 +0200
Subject: ash: tweak in comment

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 5da23c34b..9b1f57949 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -5931,7 +5931,7 @@ rmescapes(char *str, int flag)
 				 * superfluous escapes (\z where z has no
 				 * special meaning anyway). But this causes
 				 * bugs such as string of one greek letter rho
-				 * (unicode-encoded as two bytes 'cf,81")
+				 * (unicode-encoded as two bytes "cf,81")
 				 * getting encoded as "cf,CTLESC,81"
 				 * and here, converted to "cf,\,81" -
 				 * which does not go well with some flavors
-- 
cgit v1.2.3-55-g6feb


From 4142f0187dcf8454e8d2a8d16b321dbd573c170e Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 5 Jul 2017 22:19:28 +0200
Subject: ash: fix escaping of a few characters (broken by last commits)

Add a testcase which tests all ASCII punctuation escapes.
NB: hush is failing this test!

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c                                   |  9 ++++--
 shell/ash_test/ash-quoting/quoted_punct.right | 35 +++++++++++++++++++++++
 shell/ash_test/ash-quoting/quoted_punct.tests | 41 +++++++++++++++++++++++++++
 3 files changed, 83 insertions(+), 2 deletions(-)
 create mode 100644 shell/ash_test/ash-quoting/quoted_punct.right
 create mode 100755 shell/ash_test/ash-quoting/quoted_punct.tests

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 9b1f57949..946e8726e 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -5939,12 +5939,17 @@ rmescapes(char *str, int flag)
 				 * (for example, glibc <= 2.22).
 				 *
 				 * Lets add "\" only on the chars which need it.
+				 * Testcases for less obvious chars are shown.
 				 */
 				if (*p == '*'
 				 || *p == '?'
 				 || *p == '['
-				/* || *p == ']' maybe also this? */
-				 || *p == '\\'
+				 || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */
+				 || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
+				 || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
+				 || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */
+				/* Some libc support [^negate], that's why "^" also needs love */
+				 || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
 				) {
 					*q++ = '\\';
 				}
diff --git a/shell/ash_test/ash-quoting/quoted_punct.right b/shell/ash_test/ash-quoting/quoted_punct.right
new file mode 100644
index 000000000..ab66c3ce0
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quoted_punct.right
@@ -0,0 +1,35 @@
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
diff --git a/shell/ash_test/ash-quoting/quoted_punct.tests b/shell/ash_test/ash-quoting/quoted_punct.tests
new file mode 100755
index 000000000..83ee40bf4
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quoted_punct.tests
@@ -0,0 +1,41 @@
+# Testing glob-escaping of every ASCII punctuation char
+# Some chars have more than one test
+# 21..2f
+case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac
+case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac
+case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac
+case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac
+case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac
+case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac
+case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac
+case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac
+case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac
+case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac
+case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac
+case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac
+case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac
+case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac
+case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac
+case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac
+# 3a..40
+case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac
+case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac
+case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac
+case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac
+case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac
+case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac
+case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac
+# 5b..60
+case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac
+case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac
+case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac
+case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac
+case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac
+case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac
+case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac
+case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac
+# 7b..7e
+case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac
+case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac
+case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac
+case '~' in [\~] ) echo ok;; *) echo 'WRONG~';; esac
-- 
cgit v1.2.3-55-g6feb


From bd43c6784fb53826806c7cb51a1ed54e95eb4be9 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 5 Jul 2017 23:12:15 +0200
Subject: hush: fix quoted_punct.tests failure

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c                                     |  4 ++-
 shell/hush.c                                    |  6 ++--
 shell/hush_test/hush-quoting/quoted_punct.right | 35 +++++++++++++++++++++
 shell/hush_test/hush-quoting/quoted_punct.tests | 41 +++++++++++++++++++++++++
 4 files changed, 83 insertions(+), 3 deletions(-)
 create mode 100644 shell/hush_test/hush-quoting/quoted_punct.right
 create mode 100755 shell/hush_test/hush-quoting/quoted_punct.tests

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 946e8726e..b7635a823 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -7613,7 +7613,9 @@ expandhere(union node *arg, int fd)
 static int
 patmatch(char *pattern, const char *string)
 {
-	return pmatch(preglob(pattern, 0), string);
+	char *p = preglob(pattern, 0);
+	//bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string);
+	return pmatch(p, string);
 }
 
 /*
diff --git a/shell/hush.c b/shell/hush.c
index 30add72f0..7574e3918 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5969,7 +5969,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
 	return (char*)list;
 }
 
-/* Used for "eval" builtin */
+/* Used for "eval" builtin and case string */
 static char* expand_strvec_to_string(char **argv)
 {
 	char **list;
@@ -8053,6 +8053,7 @@ static int run_list(struct pipe *pi)
 		if (rword == RES_CASE) {
 			debug_printf_exec("CASE cond_code:%d\n", cond_code);
 			case_word = expand_strvec_to_string(pi->cmds->argv);
+			unbackslash(case_word);
 			continue;
 		}
 		if (rword == RES_MATCH) {
@@ -8064,9 +8065,10 @@ static int run_list(struct pipe *pi)
 			/* all prev words didn't match, does this one match? */
 			argv = pi->cmds->argv;
 			while (*argv) {
-				char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1);
+				char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0);
 				/* TODO: which FNM_xxx flags to use? */
 				cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
+				debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code);
 				free(pattern);
 				if (cond_code == 0) { /* match! we will execute this branch */
 					free(case_word);
diff --git a/shell/hush_test/hush-quoting/quoted_punct.right b/shell/hush_test/hush-quoting/quoted_punct.right
new file mode 100644
index 000000000..ab66c3ce0
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quoted_punct.right
@@ -0,0 +1,35 @@
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
diff --git a/shell/hush_test/hush-quoting/quoted_punct.tests b/shell/hush_test/hush-quoting/quoted_punct.tests
new file mode 100755
index 000000000..83ee40bf4
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quoted_punct.tests
@@ -0,0 +1,41 @@
+# Testing glob-escaping of every ASCII punctuation char
+# Some chars have more than one test
+# 21..2f
+case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac
+case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac
+case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac
+case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac
+case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac
+case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac
+case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac
+case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac
+case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac
+case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac
+case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac
+case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac
+case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac
+case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac
+case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac
+case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac
+# 3a..40
+case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac
+case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac
+case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac
+case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac
+case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac
+case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac
+case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac
+# 5b..60
+case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac
+case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac
+case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac
+case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac
+case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac
+case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac
+case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac
+case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac
+# 7b..7e
+case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac
+case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac
+case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac
+case '~' in [\~] ) echo ok;; *) echo 'WRONG~';; esac
-- 
cgit v1.2.3-55-g6feb


From 637982f5bbe7a5be4e5409ab0404df2583e7c299 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 01:52:23 +0200
Subject: hush: correctly handle quoting in "case" even if !BASH_PATTERN_SUBST

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 7574e3918..aee77d03b 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5189,7 +5189,7 @@ static struct pipe *parse_stream(char **pstring,
 /*** Execution routines ***/
 
 /* Expansion can recurse, need forward decls: */
-#if !BASH_PATTERN_SUBST
+#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
 /* only ${var/pattern/repl} (its pattern part) needs additional mode */
 #define expand_string_to_string(str, do_unbackslash) \
 	expand_string_to_string(str)
@@ -5317,6 +5317,9 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
 #endif
 static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
 {
+#if !BASH_PATTERN_SUBST
+	const int do_unbackslash = 1;
+#endif
 	char *exp_str;
 	struct in_str input;
 	o_string dest = NULL_O_STRING;
@@ -5936,7 +5939,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
  */
 static char *expand_string_to_string(const char *str, int do_unbackslash)
 {
-#if !BASH_PATTERN_SUBST
+#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
 	const int do_unbackslash = 1;
 #endif
 	char *argv[2], **list;
-- 
cgit v1.2.3-55-g6feb


From 9a8ece51582b83a2d4ed3e1854dca703d5113da2 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 17:59:25 +0200
Subject: shell: syncronize ash_test/run-all and hush_test/run-all a bit

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/run-all  | 102 +++++++++++++++++++++++++-----------------------
 shell/hush_test/run-all |  51 +++++++++++++-----------
 2 files changed, 82 insertions(+), 71 deletions(-)

(limited to 'shell')

diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 8dfdddd9f..983e6d184 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-TOPDIR=$PWD
+TOPDIR=`pwd`
 
 test -x ash || {
     echo "No ./ash - creating a link to ../../busybox"
@@ -10,67 +10,73 @@ test -x printenv || gcc -O2 -o printenv printenv.c || exit $?
 test -x recho    || gcc -O2 -o recho    recho.c    || exit $?
 test -x zecho    || gcc -O2 -o zecho    zecho.c    || exit $?
 
-PATH="$PWD:$PATH" # for ash and recho/zecho/printenv
+PATH="`pwd`:$PATH" # for ash and recho/zecho/printenv
 export PATH
 
-THIS_SH="$PWD/ash"
+THIS_SH="`pwd`/ash"
 export THIS_SH
 
 do_test()
 {
-    test -d "$1" || return 0
-#   echo do_test "$1"
-    # $1 but with / replaced by # so that it can be used as filename part
-    noslash=`echo "$1" | sed 's:/:#:g'`
-    (
-    cd "$1" || { echo "cannot cd $1!"; exit 1; }
-    for x in run-*; do
-	test -f "$x" || continue
-	case "$x" in
-	    "$0"|run-minimal|run-gprof) ;;
-	    *.orig|*~) ;;
-	    #*) echo $x ; sh $x ;;
-	    *)
-	    echo -n "$1/$x: "
-	    sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
-	    { echo "ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo "fail";
-	    ;;
-	esac
-    done
-    # Many bash run-XXX scripts just do this,
-    # no point in duplication it all over the place
-    for x in *.tests; do
-	test -x "$x" || continue
-	name="${x%%.tests}"
-	test -f "$name.right" || continue
-	echo -n "$1/$x: "
-	{
-	    "$THIS_SH" "./$x" >"$name.xx" 2>&1
-	    diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
-	    && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
-	} && echo "ok" || echo "fail"
-    done
-    )
+	test -d "$1" || return 0
+	d=${d%/}
+#	echo Running tests in directory "$1"
+	# $1 but with / replaced by # so that it can be used as filename part
+	noslash=`echo "$1" | sed 's:/:#:g'`
+	(
+	cd "$1" || { echo "cannot cd $1!"; exit 1; }
+	for x in run-*; do
+		test -f "$x" || continue
+		case "$x" in
+			"$0"|run-minimal|run-gprof) ;;
+			*.orig|*~) ;;
+			#*) echo $x ; sh $x ;;
+			*)
+			echo -n "$1/$x:"
+			sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
+			{ { echo " ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo " fail"; }
+			;;
+		esac
+	done
+	# Many bash run-XXX scripts just do this,
+	# no point in duplication it all over the place
+	for x in *.tests; do
+		test -x "$x" || continue
+		name="${x%%.tests}"
+		test -f "$name.right" || continue
+#		echo Running test: "$x"
+		echo -n "$1/$x:"
+		{
+			"$THIS_SH" "./$x" >"$name.xx" 2>&1
+			diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
+			&& rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
+		} && echo " ok" || echo " fail"
+		done
+	)
 }
 
-# main part of this script
+# Main part of this script
 # Usage: run-all [directories]
 
+ret=0
+
 if [ $# -lt 1 ]; then
-    # All sub directories
-    modules=`ls -d ash-*`
-    # If you want to test ash against hush testsuite
-    # (have to copy hush_test dir to current dir first):
-    #modules=`ls -d ash-* hush_test/hush-*`
+	# All sub directories
+	modules=`ls -d ash-*`
+	# If you want to test ash against hush testsuite
+	# (have to copy hush_test dir to current dir first):
+	#modules=`ls -d ash-* hush_test/hush-*`
 
-    for module in $modules; do
-	do_test $module
-    done
+	for module in $modules; do
+		do_test $module || ret=1
+	done
 else
-    while [ $# -ge 1 ]; do
+	while [ $# -ge 1 ]; do
 	if [ -d $1 ]; then
-	    do_test $1
+		do_test $1 || ret=1
 	fi
 	shift
-    done
+	done
 fi
+
+exit ${ret}
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all
index 837b3f7da..1dd0edc39 100755
--- a/shell/hush_test/run-all
+++ b/shell/hush_test/run-all
@@ -9,6 +9,8 @@ unset LC_NUMERIC
 unset LC_TIME
 unset LC_ALL
 
+TOPDIR=`pwd`
+
 if test ! -x hush; then
 	if test ! -x ../../busybox; then
 		echo "Can't run tests. Put hush binary into this directory (`pwd`)"
@@ -38,6 +40,8 @@ do_test()
 	test -d "$1" || return 0
 	d=${d%/}
 #	echo Running tests in directory "$1"
+	# $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; }
@@ -49,34 +53,35 @@ do_test()
 			#*) echo $x ; sh $x ;;
 			*)
 			echo -n "$1/$x:"
-			sh "$x" >"../$1-$x.fail" 2>&1 && \
-			{ { echo " ok"; rm "../$1-$x.fail"; } || echo " fail"; }
+			sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
+			{ { echo " ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo " fail"; }
 			;;
 		esac
 	done
 	# Many bash run-XXX scripts just do this,
 	# no point in duplication it all over the place
 	for x in *.tests; do
-	test -x "$x" || continue
-	name="${x%%.tests}"
-	test -f "$name.right" || continue
-#	echo Running test: "$x"
-	echo -n "$1/$x:"
-	(
-		"$THIS_SH" "./$x" >"$name.xx" 2>&1
-		r=$?
-		# filter C library differences
-		sed -i \
-			-e "/: invalid option /s:'::g" \
-			"$name.xx"
-		test $r -eq 77 && rm -f "../$1-$x.fail" && exit 77
-		diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail"
-	)
-	case $? in
-		0)  echo " ok";;
-		77) echo " skip (feature disabled)";;
-		*)  echo " fail"; tret=1;;
-	esac
+		test -x "$x" || continue
+		name="${x%%.tests}"
+		test -f "$name.right" || continue
+#		echo Running test: "$x"
+		echo -n "$1/$x:"
+		(
+			"$THIS_SH" "./$x" >"$name.xx" 2>&1
+			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"
+		)
+		case $? in
+			0)  echo " ok";;
+			77) echo " skip (feature disabled)";;
+			*)  echo " fail"; tret=1;;
+		esac
 	done
 	exit ${tret}
 	)
@@ -92,7 +97,7 @@ if [ $# -lt 1 ]; then
 	modules=`ls -d hush-*`
 
 	for module in $modules; do
-	do_test $module || ret=1
+		do_test $module || ret=1
 	done
 else
 	while [ $# -ge 1 ]; do
-- 
cgit v1.2.3-55-g6feb


From 959cb6742832a3b403a5d0116088a09f33afe927 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 18:16:18 +0200
Subject: shell: syncronize ash and hush heredoc1.tests

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-heredoc/heredoc1.right   |  6 +++++-
 shell/ash_test/ash-heredoc/heredoc1.tests   | 12 +++++++++---
 shell/ash_test/ash-heredoc/heredoc8.right   |  1 +
 shell/ash_test/ash-heredoc/heredoc8.tests   |  3 +++
 shell/hush_test/hush-heredoc/heredoc8.right |  1 +
 shell/hush_test/hush-heredoc/heredoc8.tests |  3 +++
 6 files changed, 22 insertions(+), 4 deletions(-)
 create mode 100644 shell/ash_test/ash-heredoc/heredoc8.right
 create mode 100755 shell/ash_test/ash-heredoc/heredoc8.tests
 create mode 100644 shell/hush_test/hush-heredoc/heredoc8.right
 create mode 100755 shell/hush_test/hush-heredoc/heredoc8.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-heredoc/heredoc1.right b/shell/ash_test/ash-heredoc/heredoc1.right
index 40aa5a5fe..7fc68f3e1 100644
--- a/shell/ash_test/ash-heredoc/heredoc1.right
+++ b/shell/ash_test/ash-heredoc/heredoc1.right
@@ -1 +1,5 @@
-./heredoc1.tests: line 3: syntax error: unexpected "then"
+qwe
+asd
+123
+456
+Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc1.tests b/shell/ash_test/ash-heredoc/heredoc1.tests
index a912a67c7..2eeb4726b 100755
--- a/shell/ash_test/ash-heredoc/heredoc1.tests
+++ b/shell/ash_test/ash-heredoc/heredoc1.tests
@@ -1,3 +1,9 @@
-# We used to SEGV on this:
-
-<<EOF; then <W
+cat <<000; cat <<www; cat <<eee
+000
+qwe
+asd
+www
+123
+456
+eee
+echo Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc8.right b/shell/ash_test/ash-heredoc/heredoc8.right
new file mode 100644
index 000000000..af396660e
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc8.right
@@ -0,0 +1 @@
+./heredoc8.tests: line 3: syntax error: unexpected "then"
diff --git a/shell/ash_test/ash-heredoc/heredoc8.tests b/shell/ash_test/ash-heredoc/heredoc8.tests
new file mode 100755
index 000000000..f7bc0737a
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc8.tests
@@ -0,0 +1,3 @@
+# ash used to SEGV on this:
+
+<<EOF; then <W
diff --git a/shell/hush_test/hush-heredoc/heredoc8.right b/shell/hush_test/hush-heredoc/heredoc8.right
new file mode 100644
index 000000000..558858f47
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc8.right
@@ -0,0 +1 @@
+hush: syntax error at 'then'
diff --git a/shell/hush_test/hush-heredoc/heredoc8.tests b/shell/hush_test/hush-heredoc/heredoc8.tests
new file mode 100755
index 000000000..f7bc0737a
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc8.tests
@@ -0,0 +1,3 @@
+# ash used to SEGV on this:
+
+<<EOF; then <W
-- 
cgit v1.2.3-55-g6feb


From bb963bda62cc8b0965a921df70dfea44c4378163 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 18:19:35 +0200
Subject: shell: syncronize ash and hush heredoc3.tests

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-heredoc/heredoc3.right | 10 +++++++++-
 shell/ash_test/ash-heredoc/heredoc3.tests | 21 ++++++++++++---------
 shell/ash_test/ash-heredoc/heredoc9.right |  1 +
 shell/ash_test/ash-heredoc/heredoc9.tests |  9 +++++++++
 4 files changed, 31 insertions(+), 10 deletions(-)
 create mode 100644 shell/ash_test/ash-heredoc/heredoc9.right
 create mode 100755 shell/ash_test/ash-heredoc/heredoc9.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-heredoc/heredoc3.right b/shell/ash_test/ash-heredoc/heredoc3.right
index ce0136250..6ed517f74 100644
--- a/shell/ash_test/ash-heredoc/heredoc3.right
+++ b/shell/ash_test/ash-heredoc/heredoc3.right
@@ -1 +1,9 @@
-hello
+exit EOF-f
+"
+echo $f
+echo `echo Hello World`
+moo	 
+ EOF-f
+EOF-f   f
+EOF-f 
+Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc3.tests b/shell/ash_test/ash-heredoc/heredoc3.tests
index 96c227cc1..938577a89 100755
--- a/shell/ash_test/ash-heredoc/heredoc3.tests
+++ b/shell/ash_test/ash-heredoc/heredoc3.tests
@@ -1,9 +1,12 @@
-echo hello >greeting
-cat <<EOF &&
-$(cat greeting)
-EOF
-{
-	echo $?
-	cat greeting
-} >/dev/null
-rm greeting
+f=1
+  cat <<- EOF-f""
+		exit EOF-f
+"
+echo $f
+echo `echo Hello World`
+		moo	 
+ EOF-f
+EOF-f   f
+EOF-f 
+EOF-f
+echo Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc9.right b/shell/ash_test/ash-heredoc/heredoc9.right
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc9.right
@@ -0,0 +1 @@
+hello
diff --git a/shell/ash_test/ash-heredoc/heredoc9.tests b/shell/ash_test/ash-heredoc/heredoc9.tests
new file mode 100755
index 000000000..96c227cc1
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc9.tests
@@ -0,0 +1,9 @@
+echo hello >greeting
+cat <<EOF &&
+$(cat greeting)
+EOF
+{
+	echo $?
+	cat greeting
+} >/dev/null
+rm greeting
-- 
cgit v1.2.3-55-g6feb


From cafb2d195d4d68e4e4c474453409a69643edf5aa Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 18:31:47 +0200
Subject: hush: add tickquote1.tests from ash testsuite

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/tickquote1.tests   | 2 +-
 shell/hush_test/hush-misc/tickquote1.right | 1 +
 shell/hush_test/hush-misc/tickquote1.tests | 2 ++
 3 files changed, 4 insertions(+), 1 deletion(-)
 create mode 100644 shell/hush_test/hush-misc/tickquote1.right
 create mode 100755 shell/hush_test/hush-misc/tickquote1.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/tickquote1.tests b/shell/ash_test/ash-misc/tickquote1.tests
index 90d5bbc9b..8416ad996 100755
--- a/shell/ash_test/ash-misc/tickquote1.tests
+++ b/shell/ash_test/ash-misc/tickquote1.tests
@@ -1 +1 @@
-echo `"pwd`
+echo _`"pwd`_
diff --git a/shell/hush_test/hush-misc/tickquote1.right b/shell/hush_test/hush-misc/tickquote1.right
new file mode 100644
index 000000000..56f8515b7
--- /dev/null
+++ b/shell/hush_test/hush-misc/tickquote1.right
@@ -0,0 +1 @@
+hush: syntax error: unterminated "
diff --git a/shell/hush_test/hush-misc/tickquote1.tests b/shell/hush_test/hush-misc/tickquote1.tests
new file mode 100755
index 000000000..02e3904f1
--- /dev/null
+++ b/shell/hush_test/hush-misc/tickquote1.tests
@@ -0,0 +1,2 @@
+# UNFIXED BUG: hush does not parse embedded `cmd` at embedding document parse time
+echo _`"pwd`_
-- 
cgit v1.2.3-55-g6feb


From b18b04c8a832148fb47aec20ce1e74267d2fb438 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 18:37:30 +0200
Subject: shell: remove duplicate sigint1.tests (another copies are in
 signals/)

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/sigint1.right   |  1 -
 shell/ash_test/ash-misc/sigint1.tests   | 41 ---------------------------------
 shell/hush_test/hush-misc/sigint1.right |  1 -
 shell/hush_test/hush-misc/sigint1.tests | 41 ---------------------------------
 4 files changed, 84 deletions(-)
 delete mode 100644 shell/ash_test/ash-misc/sigint1.right
 delete mode 100755 shell/ash_test/ash-misc/sigint1.tests
 delete mode 100644 shell/hush_test/hush-misc/sigint1.right
 delete mode 100755 shell/hush_test/hush-misc/sigint1.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/sigint1.right b/shell/ash_test/ash-misc/sigint1.right
deleted file mode 100644
index a9094b056..000000000
--- a/shell/ash_test/ash-misc/sigint1.right
+++ /dev/null
@@ -1 +0,0 @@
-Sending SIGINT to main shell PID
diff --git a/shell/ash_test/ash-misc/sigint1.tests b/shell/ash_test/ash-misc/sigint1.tests
deleted file mode 100755
index 3d483d32a..000000000
--- a/shell/ash_test/ash-misc/sigint1.tests
+++ /dev/null
@@ -1,41 +0,0 @@
-# What should happen if non-interactive shell gets SIGINT?
-
-(sleep 1; echo Sending SIGINT to main shell PID; exec kill -INT $$) &
-
-# We create a child which exits with 0 even on SIGINT
-# (The complex command is necessary only if SIGINT is generated by ^C,
-# in this testcase even bare "sleep 2" would do because
-# in the testcase we don't send SIGINT *to the child*...)
-$THIS_SH -c 'trap "exit 0" SIGINT; sleep 2'
-
-# In one second, we (main shell) get SIGINT here.
-# The question is whether we should, or should not, exit.
-
-# bash will not stop here. It will execute next command(s).
-
-# The rationale for this is described here:
-# http://www.cons.org/cracauer/sigint.html
-#
-# Basically, bash will not exit on SIGINT immediately if it waits
-# for a child. It will wait for the child to exit.
-# If child exits NOT by dying on SIGINT, then bash will not exit.
-#
-# The idea is that the following script:
-# | emacs file.txt
-# | more cmds
-# User may use ^C to interrupt editor's ops like search. But then
-# emacs exits normally. User expects that script doesn't stop.
-#
-# This is a nice idea, but detecting "did process really exit
-# with SIGINT?" is racy. Consider:
-# | bash -c 'while true; do /bin/true; done'
-# When ^C is pressed while bash waits for /bin/true to exit,
-# it may happen that /bin/true exits with exitcode 0 before
-# ^C is delivered to it as SIGINT. bash will see SIGINT, then
-# it will see that child exited with 0, and bash will NOT EXIT.
-
-# Therefore we do not implement bash behavior.
-# I'd say that emacs need to put itself into a separate pgrp
-# to isolate shell from getting stray SIGINTs from ^C.
-
-echo Next command after SIGINT was executed
diff --git a/shell/hush_test/hush-misc/sigint1.right b/shell/hush_test/hush-misc/sigint1.right
deleted file mode 100644
index a9094b056..000000000
--- a/shell/hush_test/hush-misc/sigint1.right
+++ /dev/null
@@ -1 +0,0 @@
-Sending SIGINT to main shell PID
diff --git a/shell/hush_test/hush-misc/sigint1.tests b/shell/hush_test/hush-misc/sigint1.tests
deleted file mode 100755
index 3d483d32a..000000000
--- a/shell/hush_test/hush-misc/sigint1.tests
+++ /dev/null
@@ -1,41 +0,0 @@
-# What should happen if non-interactive shell gets SIGINT?
-
-(sleep 1; echo Sending SIGINT to main shell PID; exec kill -INT $$) &
-
-# We create a child which exits with 0 even on SIGINT
-# (The complex command is necessary only if SIGINT is generated by ^C,
-# in this testcase even bare "sleep 2" would do because
-# in the testcase we don't send SIGINT *to the child*...)
-$THIS_SH -c 'trap "exit 0" SIGINT; sleep 2'
-
-# In one second, we (main shell) get SIGINT here.
-# The question is whether we should, or should not, exit.
-
-# bash will not stop here. It will execute next command(s).
-
-# The rationale for this is described here:
-# http://www.cons.org/cracauer/sigint.html
-#
-# Basically, bash will not exit on SIGINT immediately if it waits
-# for a child. It will wait for the child to exit.
-# If child exits NOT by dying on SIGINT, then bash will not exit.
-#
-# The idea is that the following script:
-# | emacs file.txt
-# | more cmds
-# User may use ^C to interrupt editor's ops like search. But then
-# emacs exits normally. User expects that script doesn't stop.
-#
-# This is a nice idea, but detecting "did process really exit
-# with SIGINT?" is racy. Consider:
-# | bash -c 'while true; do /bin/true; done'
-# When ^C is pressed while bash waits for /bin/true to exit,
-# it may happen that /bin/true exits with exitcode 0 before
-# ^C is delivered to it as SIGINT. bash will see SIGINT, then
-# it will see that child exited with 0, and bash will NOT EXIT.
-
-# Therefore we do not implement bash behavior.
-# I'd say that emacs need to put itself into a separate pgrp
-# to isolate shell from getting stray SIGINTs from ^C.
-
-echo Next command after SIGINT was executed
-- 
cgit v1.2.3-55-g6feb


From 74562984720c3f7d0a62ffbfd8ee78391c39c1e8 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 18:40:45 +0200
Subject: hush: "adopt" ash signal4.tests

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                               | 2 +-
 shell/hush_test/hush-signals/signal4.right | 4 ++++
 shell/hush_test/hush-signals/signal4.tests | 5 +++++
 3 files changed, 10 insertions(+), 1 deletion(-)
 create mode 100644 shell/hush_test/hush-signals/signal4.right
 create mode 100755 shell/hush_test/hush-signals/signal4.tests

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index aee77d03b..64b33cf1c 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9486,7 +9486,7 @@ static int FAST_FUNC builtin_trap(char **argv)
 			if (sig < 0 || sig >= NSIG) {
 				ret = EXIT_FAILURE;
 				/* Mimic bash message exactly */
-				bb_perror_msg("trap: %s: invalid signal specification", argv[-1]);
+				bb_error_msg("trap: %s: invalid signal specification", argv[-1]);
 				continue;
 			}
 
diff --git a/shell/hush_test/hush-signals/signal4.right b/shell/hush_test/hush-signals/signal4.right
new file mode 100644
index 000000000..2d0624714
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal4.right
@@ -0,0 +1,4 @@
+hush: trap: BADNAME: invalid signal specification
+1
+Trapped
+Ok
diff --git a/shell/hush_test/hush-signals/signal4.tests b/shell/hush_test/hush-signals/signal4.tests
new file mode 100755
index 000000000..6f1c4a950
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal4.tests
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+trap "echo Trapped" BADNAME TERM; echo $?
+kill $$
+echo Ok
-- 
cgit v1.2.3-55-g6feb


From 3234045d07c3fb2a9ef8afd02f821158317adbd3 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 19:29:23 +0200
Subject: hush: "adopt" ash var-utf8-length.tests

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-vars/var-utf8-length.tests   | 2 ++
 shell/hush_test/hush-vars/var-utf8-length.right | 1 +
 shell/hush_test/hush-vars/var-utf8-length.tests | 4 ++++
 3 files changed, 7 insertions(+)
 create mode 100644 shell/hush_test/hush-vars/var-utf8-length.right
 create mode 100755 shell/hush_test/hush-vars/var-utf8-length.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-vars/var-utf8-length.tests b/shell/ash_test/ash-vars/var-utf8-length.tests
index d04b2cbb6..b6e87f191 100755
--- a/shell/ash_test/ash-vars/var-utf8-length.tests
+++ b/shell/ash_test/ash-vars/var-utf8-length.tests
@@ -1,2 +1,4 @@
+LANG=en_US.UTF-8
+LC_ALL=en_US.UTF-8
 X=abcdÉfghÍjklmnÓpqrstÚvwcyz
 echo ${#X}
diff --git a/shell/hush_test/hush-vars/var-utf8-length.right b/shell/hush_test/hush-vars/var-utf8-length.right
new file mode 100644
index 000000000..6f4247a62
--- /dev/null
+++ b/shell/hush_test/hush-vars/var-utf8-length.right
@@ -0,0 +1 @@
+26
diff --git a/shell/hush_test/hush-vars/var-utf8-length.tests b/shell/hush_test/hush-vars/var-utf8-length.tests
new file mode 100755
index 000000000..b6e87f191
--- /dev/null
+++ b/shell/hush_test/hush-vars/var-utf8-length.tests
@@ -0,0 +1,4 @@
+LANG=en_US.UTF-8
+LC_ALL=en_US.UTF-8
+X=abcdÉfghÍjklmnÓpqrstÚvwcyz
+echo ${#X}
-- 
cgit v1.2.3-55-g6feb


From 5dad7bdc3bdad8e9934d45301d5a8c51e843cd7b Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 19:48:20 +0200
Subject: hush: implement negative start in the ${v: -n[:m]} idiom

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-vars/var_bash6.right    |  5 +++++
 shell/ash_test/ash-vars/var_bash6.tests    |  9 +++++++++
 shell/hush.c                               |  8 ++++++--
 shell/hush_test/hush-vars/var_bash1a.right |  6 ++++++
 shell/hush_test/hush-vars/var_bash1a.tests | 11 +++++++++++
 5 files changed, 37 insertions(+), 2 deletions(-)
 create mode 100644 shell/ash_test/ash-vars/var_bash6.right
 create mode 100755 shell/ash_test/ash-vars/var_bash6.tests
 create mode 100644 shell/hush_test/hush-vars/var_bash1a.right
 create mode 100755 shell/hush_test/hush-vars/var_bash1a.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-vars/var_bash6.right b/shell/ash_test/ash-vars/var_bash6.right
new file mode 100644
index 000000000..63fc23df8
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash6.right
@@ -0,0 +1,5 @@
+Expected Actual
+a*z    : a*z
+\z     : \z
+a1z a2z: a1z a2z
+z      : z
diff --git a/shell/ash_test/ash-vars/var_bash6.tests b/shell/ash_test/ash-vars/var_bash6.tests
new file mode 100755
index 000000000..cf2e4f020
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash6.tests
@@ -0,0 +1,9 @@
+# This testcase checks globbing correctness in ${v/a/b}
+
+>a1z; >a2z;
+          echo 'Expected' 'Actual'
+v='a bz'; echo 'a*z    :' "${v/a*z/a*z}"
+v='a bz'; echo '\z     :' "${v/a*z/\z}"
+v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z}
+v='a bz'; echo 'z      :' ${v/a*z/\z}
+rm a1z a2z
diff --git a/shell/hush.c b/shell/hush.c
index 64b33cf1c..f6b50dec6 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5619,8 +5619,12 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				goto arith_err;
 			debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
 			if (len >= 0) { /* bash compat: len < 0 is illegal */
-				if (beg < 0) /* bash compat */
-					beg = 0;
+				if (beg < 0) {
+					/* negative beg counts from the end */
+					beg = (arith_t)strlen(val) + beg;
+					if (beg < 0) /* ${v: -999999} is "" */
+						beg = len = 0;
+				}
 				debug_printf_varexp("from val:'%s'\n", val);
 				if (len == 0 || !val || beg >= strlen(val)) {
  arith_err:
diff --git a/shell/hush_test/hush-vars/var_bash1a.right b/shell/hush_test/hush-vars/var_bash1a.right
new file mode 100644
index 000000000..1965b5c6c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1a.right
@@ -0,0 +1,6 @@
+parameter     'abcdef'
+varoffset2    'cdef'
+varoffset-2   'ef'
+literal '2'   'cdef'
+literal '-2'  'abcdef'
+literal ' -2' 'ef'
diff --git a/shell/hush_test/hush-vars/var_bash1a.tests b/shell/hush_test/hush-vars/var_bash1a.tests
new file mode 100755
index 000000000..551dd9acc
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1a.tests
@@ -0,0 +1,11 @@
+parameter=abcdef
+offset=2
+noffset=-2
+echo "parameter     '${parameter}'"
+echo "varoffset2    '${parameter:${offset}}'"
+echo "varoffset-2   '${parameter:${noffset}}'"
+echo "literal '2'   '${parameter:2}'"
+# This is not inrpreted as ${VAR:POS{:LEN}},
+# but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD
+echo "literal '-2'  '${parameter:-2}'"
+echo "literal ' -2' '${parameter: -2}'"
-- 
cgit v1.2.3-55-g6feb


From 74d20e637982b412611dbbf32b633857b1c73539 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 19:50:42 +0200
Subject: typo fix

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-vars/var_bash1a.tests   | 2 +-
 shell/hush_test/hush-vars/var_bash1a.tests | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

(limited to 'shell')

diff --git a/shell/ash_test/ash-vars/var_bash1a.tests b/shell/ash_test/ash-vars/var_bash1a.tests
index 551dd9acc..c75c2dc53 100755
--- a/shell/ash_test/ash-vars/var_bash1a.tests
+++ b/shell/ash_test/ash-vars/var_bash1a.tests
@@ -5,7 +5,7 @@ echo "parameter     '${parameter}'"
 echo "varoffset2    '${parameter:${offset}}'"
 echo "varoffset-2   '${parameter:${noffset}}'"
 echo "literal '2'   '${parameter:2}'"
-# This is not inrpreted as ${VAR:POS{:LEN}},
+# This is not interpreted as ${VAR:POS{:LEN}},
 # but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD
 echo "literal '-2'  '${parameter:-2}'"
 echo "literal ' -2' '${parameter: -2}'"
diff --git a/shell/hush_test/hush-vars/var_bash1a.tests b/shell/hush_test/hush-vars/var_bash1a.tests
index 551dd9acc..c75c2dc53 100755
--- a/shell/hush_test/hush-vars/var_bash1a.tests
+++ b/shell/hush_test/hush-vars/var_bash1a.tests
@@ -5,7 +5,7 @@ echo "parameter     '${parameter}'"
 echo "varoffset2    '${parameter:${offset}}'"
 echo "varoffset-2   '${parameter:${noffset}}'"
 echo "literal '2'   '${parameter:2}'"
-# This is not inrpreted as ${VAR:POS{:LEN}},
+# This is not interpreted as ${VAR:POS{:LEN}},
 # but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD
 echo "literal '-2'  '${parameter:-2}'"
 echo "literal ' -2' '${parameter: -2}'"
-- 
cgit v1.2.3-55-g6feb


From e59591a364e43bccb6fd4d373d3ed86fc77dffb7 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 20:12:44 +0200
Subject: hush: Print error messages on shift -1

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/shift1.tests   |  2 +-
 shell/hush.c                           | 13 ++++++++++++-
 shell/hush_test/hush-misc/shift1.right | 10 ++++++++++
 shell/hush_test/hush-misc/shift1.tests | 10 ++++++++++
 4 files changed, 33 insertions(+), 2 deletions(-)
 create mode 100644 shell/hush_test/hush-misc/shift1.right
 create mode 100755 shell/hush_test/hush-misc/shift1.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/shift1.tests b/shell/ash_test/ash-misc/shift1.tests
index 0992d9b1b..2774b35ea 100755
--- a/shell/ash_test/ash-misc/shift1.tests
+++ b/shell/ash_test/ash-misc/shift1.tests
@@ -1,5 +1,5 @@
 $THIS_SH -c 'shift;    echo "$@"' 0 1 2 3 4
-#We do abort on -1, but then we abort. bash executes echo.
+# We do complain on -1, but then we abort. bash executes echo.
 $THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4
 $THIS_SH -c 'shift  0; echo "$@"' 0 1 2 3 4
 $THIS_SH -c 'shift  1; echo "$@"' 0 1 2 3 4
diff --git a/shell/hush.c b/shell/hush.c
index f6b50dec6..0ade2ccca 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9377,7 +9377,18 @@ static int FAST_FUNC builtin_shift(char **argv)
 	int n = 1;
 	argv = skip_dash_dash(argv);
 	if (argv[0]) {
-		n = atoi(argv[0]);
+		n = bb_strtou(argv[0], NULL, 10);
+		if (errno || n < 0) {
+			/* shared string with ash.c */
+			bb_error_msg("Illegal number: %s", argv[0]);
+			/*
+			 * ash aborts in this case.
+			 * bash prints error message and set $? to 1.
+			 * Interestingly, for "shift 99999" bash does not
+			 * print error message, but does set $? to 1
+			 * (and does no shifting at all).
+			 */
+		}
 	}
 	if (n >= 0 && n < G.global_argc) {
 		if (G_global_args_malloced) {
diff --git a/shell/hush_test/hush-misc/shift1.right b/shell/hush_test/hush-misc/shift1.right
new file mode 100644
index 000000000..e3ab61392
--- /dev/null
+++ b/shell/hush_test/hush-misc/shift1.right
@@ -0,0 +1,10 @@
+2 3 4
+hush: Illegal number: -1
+1 2 3 4
+1 2 3 4
+2 3 4
+3 4
+4
+
+1 2 3 4
+1 2 3 4
diff --git a/shell/hush_test/hush-misc/shift1.tests b/shell/hush_test/hush-misc/shift1.tests
new file mode 100755
index 000000000..f2a264751
--- /dev/null
+++ b/shell/hush_test/hush-misc/shift1.tests
@@ -0,0 +1,10 @@
+$THIS_SH -c 'shift;    echo "$@"' 0 1 2 3 4
+#We complain on -1 and continue.
+$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  0; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  1; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  2; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  3; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  4; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  5; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  6; echo "$@"' 0 1 2 3 4
-- 
cgit v1.2.3-55-g6feb


From a107ef2a6ace98c51473dc3153564a44b260bc6f Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 20:36:40 +0200
Subject: hush: rename hush-redir/redir3.tests (ash has redir3.tests which id
 different)

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush_test/hush-redir/redir3.right       | 14 --------------
 shell/hush_test/hush-redir/redir3.tests       |  9 ---------
 shell/hush_test/hush-redir/redir_errors.right | 14 ++++++++++++++
 shell/hush_test/hush-redir/redir_errors.tests |  9 +++++++++
 4 files changed, 23 insertions(+), 23 deletions(-)
 delete mode 100644 shell/hush_test/hush-redir/redir3.right
 delete mode 100755 shell/hush_test/hush-redir/redir3.tests
 create mode 100644 shell/hush_test/hush-redir/redir_errors.right
 create mode 100755 shell/hush_test/hush-redir/redir_errors.tests

(limited to 'shell')

diff --git a/shell/hush_test/hush-redir/redir3.right b/shell/hush_test/hush-redir/redir3.right
deleted file mode 100644
index 3d20bbf68..000000000
--- a/shell/hush_test/hush-redir/redir3.right
+++ /dev/null
@@ -1,14 +0,0 @@
-hush: can't open '/does/not/exist': No such file or directory
-One:1
-hush: can't open '/cant/be/created': No such file or directory
-One:1
-Ok
-hush: can't open '/cant/be/created': No such file or directory
-Zero:0
-hush: can't open '/cant/be/created': No such file or directory
-One:1
-hush: can't open '/cant/be/created': No such file or directory
-One:1
-hush: can't open '/cant/be/created': No such file or directory
-Zero:0
-Done
diff --git a/shell/hush_test/hush-redir/redir3.tests b/shell/hush_test/hush-redir/redir3.tests
deleted file mode 100755
index 7c28e4324..000000000
--- a/shell/hush_test/hush-redir/redir3.tests
+++ /dev/null
@@ -1,9 +0,0 @@
-echo Error >/does/not/exist; echo One:$?
-t=BAD
-t=Ok >>/cant/be/created; echo One:$?
-echo $t
-! >/cant/be/created; echo Zero:$?
-exec >/cant/be/created; echo One:$?
-exec /bin/true >/cant/be/created; echo One:$?
-! exec /bin/true >/cant/be/created; echo Zero:$?
-echo Done
diff --git a/shell/hush_test/hush-redir/redir_errors.right b/shell/hush_test/hush-redir/redir_errors.right
new file mode 100644
index 000000000..3d20bbf68
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_errors.right
@@ -0,0 +1,14 @@
+hush: can't open '/does/not/exist': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+Ok
+hush: can't open '/cant/be/created': No such file or directory
+Zero:0
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+Zero:0
+Done
diff --git a/shell/hush_test/hush-redir/redir_errors.tests b/shell/hush_test/hush-redir/redir_errors.tests
new file mode 100755
index 000000000..7c28e4324
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_errors.tests
@@ -0,0 +1,9 @@
+echo Error >/does/not/exist; echo One:$?
+t=BAD
+t=Ok >>/cant/be/created; echo One:$?
+echo $t
+! >/cant/be/created; echo Zero:$?
+exec >/cant/be/created; echo One:$?
+exec /bin/true >/cant/be/created; echo One:$?
+! exec /bin/true >/cant/be/created; echo Zero:$?
+echo Done
-- 
cgit v1.2.3-55-g6feb


From 50b8b2914b7551b4e36518fcc70aac201d46d7cb Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 20:57:37 +0200
Subject: hush: add a TODO about redir3.tests failure

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                            | 4 ++++
 shell/hush_test/hush-redir/redir3.right | 3 +++
 shell/hush_test/hush-redir/redir3.tests | 5 +++++
 3 files changed, 12 insertions(+)
 create mode 100644 shell/hush_test/hush-redir/redir3.right
 create mode 100755 shell/hush_test/hush-redir/redir3.tests

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 0ade2ccca..4ba6b3fdd 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7723,6 +7723,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			unset_vars(new_env);
 			add_vars(old_vars);
 /* clean_up_and_ret0: */
+
+//FIXME: this restores stdio fds, but does not close other redirects!
+//Example: after "echo TEST 9>/dev/null" fd#9 is not closed!
+//The squirreling code needs rework to remember all fds, not just 0,1,2.
 			restore_redirects(squirrel);
  clean_up_and_ret1:
 			free(argv_expanded);
diff --git a/shell/hush_test/hush-redir/redir3.right b/shell/hush_test/hush-redir/redir3.right
new file mode 100644
index 000000000..fd641a8ea
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir3.right
@@ -0,0 +1,3 @@
+TEST
+./redir3.tests: line 4: 9: Bad file descriptor
+Output to fd#9: 1
diff --git a/shell/hush_test/hush-redir/redir3.tests b/shell/hush_test/hush-redir/redir3.tests
new file mode 100755
index 000000000..e37d5e45a
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir3.tests
@@ -0,0 +1,5 @@
+# redirects to closed descriptors should not leave these descriptors
+# open afterwards
+echo TEST 9>/dev/null
+echo MUST ERROR OUT >&9
+echo "Output to fd#9: $?"
-- 
cgit v1.2.3-55-g6feb


From 1ff1a75710dd0c2d72d1f97f71caebdfeb185f6d Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 21:00:19 +0200
Subject: ash: rename redir5.tests (hush has redir5.tests which is different)

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-redir/redir5.right          | 2 --
 shell/ash_test/ash-redir/redir5.tests          | 3 ---
 shell/ash_test/ash-redir/redir_to_bad_fd.right | 2 ++
 shell/ash_test/ash-redir/redir_to_bad_fd.tests | 3 +++
 4 files changed, 5 insertions(+), 5 deletions(-)
 delete mode 100644 shell/ash_test/ash-redir/redir5.right
 delete mode 100755 shell/ash_test/ash-redir/redir5.tests
 create mode 100644 shell/ash_test/ash-redir/redir_to_bad_fd.right
 create mode 100755 shell/ash_test/ash-redir/redir_to_bad_fd.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-redir/redir5.right b/shell/ash_test/ash-redir/redir5.right
deleted file mode 100644
index 9d0877765..000000000
--- a/shell/ash_test/ash-redir/redir5.right
+++ /dev/null
@@ -1,2 +0,0 @@
-./redir5.tests: line 2: 10: Bad file descriptor
-OK
diff --git a/shell/ash_test/ash-redir/redir5.tests b/shell/ash_test/ash-redir/redir5.tests
deleted file mode 100755
index 91b0c1ff9..000000000
--- a/shell/ash_test/ash-redir/redir5.tests
+++ /dev/null
@@ -1,3 +0,0 @@
-# ash uses fd 10 (usually) for reading the script
-echo LOST >&10
-echo OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd.right b/shell/ash_test/ash-redir/redir_to_bad_fd.right
new file mode 100644
index 000000000..43b8af293
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd.right
@@ -0,0 +1,2 @@
+./redir_to_bad_fd.tests: line 2: 10: Bad file descriptor
+OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd.tests b/shell/ash_test/ash-redir/redir_to_bad_fd.tests
new file mode 100755
index 000000000..91b0c1ff9
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd.tests
@@ -0,0 +1,3 @@
+# ash uses fd 10 (usually) for reading the script
+echo LOST >&10
+echo OK
-- 
cgit v1.2.3-55-g6feb


From 111cdcf295b4cab78521480f52b295d9ae719263 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 6 Jul 2017 21:01:50 +0200
Subject: shell: sync redir/* tests

Note: hush-redir/redir_to_bad_fd.tests currently fails

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-redir/redir5.right            |  4 ++++
 shell/ash_test/ash-redir/redir5.tests            | 13 +++++++++++++
 shell/hush_test/hush-redir/redir_to_bad_fd.right |  2 ++
 shell/hush_test/hush-redir/redir_to_bad_fd.tests |  3 +++
 4 files changed, 22 insertions(+)
 create mode 100644 shell/ash_test/ash-redir/redir5.right
 create mode 100755 shell/ash_test/ash-redir/redir5.tests
 create mode 100644 shell/hush_test/hush-redir/redir_to_bad_fd.right
 create mode 100755 shell/hush_test/hush-redir/redir_to_bad_fd.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-redir/redir5.right b/shell/ash_test/ash-redir/redir5.right
new file mode 100644
index 000000000..52cce4feb
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir5.right
@@ -0,0 +1,4 @@
+Backgrounded pipes shall have their stdin redirected to /dev/null
+Zero:0
+Zero:0
+Done
diff --git a/shell/ash_test/ash-redir/redir5.tests b/shell/ash_test/ash-redir/redir5.tests
new file mode 100755
index 000000000..957f9c81f
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir5.tests
@@ -0,0 +1,13 @@
+echo "Backgrounded pipes shall have their stdin redirected to /dev/null"
+
+# 1. bash does not redirect stdin to /dev/null if it is interactive.
+# hush does it always (this is allowed by standards).
+
+# 2. Failure will result in this script hanging
+
+cat & wait; echo Zero:$?
+
+# This does not work for bash! bash bug?
+cat | cat & wait; echo Zero:$?
+
+echo Done
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.right b/shell/hush_test/hush-redir/redir_to_bad_fd.right
new file mode 100644
index 000000000..43b8af293
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.right
@@ -0,0 +1,2 @@
+./redir_to_bad_fd.tests: line 2: 10: Bad file descriptor
+OK
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.tests b/shell/hush_test/hush-redir/redir_to_bad_fd.tests
new file mode 100755
index 000000000..91b0c1ff9
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.tests
@@ -0,0 +1,3 @@
+# ash uses fd 10 (usually) for reading the script
+echo LOST >&10
+echo OK
-- 
cgit v1.2.3-55-g6feb


From 69a5ec9dccfd183cdf6bee7b994336670755cd47 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 7 Jul 2017 19:08:56 +0200
Subject: main: fix the case where user has "halt" as login shell. Closes 9986

halt::0:0::/:/sbin/halt

function                                             old     new   delta
run_applet_and_exit                                  748     751      +3
run_applet_no_and_exit                               467     459      -8

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 include/libbb.h            |  2 +-
 libbb/appletlib.c          | 10 +++++++---
 libbb/vfork_daemon_rexec.c |  2 +-
 shell/ash.c                |  2 +-
 shell/hush.c               |  2 +-
 5 files changed, 11 insertions(+), 7 deletions(-)

(limited to 'shell')

diff --git a/include/libbb.h b/include/libbb.h
index 1c9de3af0..0317c7d6a 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1117,7 +1117,7 @@ int spawn_and_wait(char **argv) FAST_FUNC;
 int run_nofork_applet(int applet_no, char **argv) FAST_FUNC;
 #ifndef BUILD_INDIVIDUAL
 extern int find_applet_by_name(const char *name) FAST_FUNC;
-extern void run_applet_no_and_exit(int a, char **argv) NORETURN FAST_FUNC;
+extern void run_applet_no_and_exit(int a, const char *name, char **argv) NORETURN FAST_FUNC;
 #endif
 
 /* Helpers for daemonization.
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index 2dea2b43a..df6584978 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -877,13 +877,17 @@ static int busybox_main(char **argv)
 # endif
 
 # if NUM_APPLETS > 0
-void FAST_FUNC run_applet_no_and_exit(int applet_no, char **argv)
+void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **argv)
 {
 	int argc = string_array_len(argv);
 
 	/* Reinit some shared global data */
 	xfunc_error_retval = EXIT_FAILURE;
-	applet_name = bb_get_last_path_component_nostrip(argv[0]);
+	/*
+	 * We do not use argv[0]: do not want to repeat massaging of
+	 * "-/sbin/halt" -> "halt", for example.
+	 */
+	applet_name = name;
 
 	/* Special case. POSIX says "test --help"
 	 * should be no different from e.g. "test --foo".
@@ -927,7 +931,7 @@ static NORETURN void run_applet_and_exit(const char *name, char **argv)
 	{
 		int applet = find_applet_by_name(name);
 		if (applet >= 0)
-			run_applet_no_and_exit(applet, argv);
+			run_applet_no_and_exit(applet, name, argv);
 	}
 #  endif
 
diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c
index 2695f99ee..576534ee5 100644
--- a/libbb/vfork_daemon_rexec.c
+++ b/libbb/vfork_daemon_rexec.c
@@ -180,7 +180,7 @@ int FAST_FUNC spawn_and_wait(char **argv)
 			 * as of yet (and that should probably always stay true).
 			 */
 			/* xfunc_error_retval and applet_name are init by: */
-			run_applet_no_and_exit(a, argv);
+			run_applet_no_and_exit(a, argv[0], argv);
 		}
 # endif
 	}
diff --git a/shell/ash.c b/shell/ash.c
index b7635a823..8c2098dd9 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -7717,7 +7717,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
 			clearenv();
 			while (*envp)
 				putenv(*envp++);
-			run_applet_no_and_exit(applet_no, argv);
+			run_applet_no_and_exit(applet_no, cmd, argv);
 		}
 		/* re-exec ourselves with the new arguments */
 		execve(bb_busybox_exec_path, argv, envp);
diff --git a/shell/hush.c b/shell/hush.c
index 4ba6b3fdd..cf6d8cd9f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7063,7 +7063,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 				/* Do not leak open fds from opened script files etc */
 				close_all_FILE_list();
 				debug_printf_exec("running applet '%s'\n", argv[0]);
-				run_applet_no_and_exit(a, argv);
+				run_applet_no_and_exit(a, argv[0], argv);
 			}
 # endif
 			/* Re-exec ourselves */
-- 
cgit v1.2.3-55-g6feb


From 2db74610cdf8ffb4f9ed99b62c755377d3cc48ea Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 7 Jul 2017 22:07:28 +0200
Subject: hush: fix two redirection testcase failures

function                                             old     new   delta
save_fds_on_redirect                                 183     256     +73
fcntl_F_DUPFD                                          -      46     +46
restore_redirects                                     74      96     +22
xdup_and_close                                        51      72     +21
setup_redirects                                      196     200      +4
hush_main                                            988     983      -5
static.C                                              12       -     -12
run_pipe                                            1595    1551     -44
------------------------------------------------------------------------------
(add/remove: 1/1 grow/shrink: 4/2 up/down: 166/-61)           Total: 105 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                                     | 179 +++++++++++++++--------
 shell/hush_test/hush-redir/redir3.right          |   3 +-
 shell/hush_test/hush-redir/redir_to_bad_fd.right |   3 +-
 3 files changed, 117 insertions(+), 68 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index cf6d8cd9f..59bddbfff 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -402,6 +402,7 @@
 #define debug_printf_expand(...) do {} while (0)
 #define debug_printf_varexp(...) do {} while (0)
 #define debug_printf_glob(...)   do {} while (0)
+#define debug_printf_redir(...)  do {} while (0)
 #define debug_printf_list(...)   do {} while (0)
 #define debug_printf_subst(...)  do {} while (0)
 #define debug_printf_clean(...)  do {} while (0)
@@ -1146,6 +1147,10 @@ static const struct built_in_command bltins2[] = {
 # define DEBUG_GLOB 0
 #endif
 
+#ifndef debug_printf_redir
+# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
 #ifndef debug_printf_list
 # define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
@@ -1381,12 +1386,30 @@ static void free_strings(char **strings)
 	free(strings);
 }
 
+static int fcntl_F_DUPFD(int fd, int avoid_fd)
+{
+	int newfd;
+ repeat:
+	newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
+	if (newfd < 0) {
+		if (errno == EBUSY)
+			goto repeat;
+		if (errno == EINTR)
+			goto repeat;
+	}
+	return newfd;
+}
 
-static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC)
+static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
 {
-	/* We avoid taking stdio fds. Mimicking ash: use fds above 9 */
-	int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10);
+	int newfd;
+ repeat:
+	newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1);
 	if (newfd < 0) {
+		if (errno == EBUSY)
+			goto repeat;
+		if (errno == EINTR)
+			goto repeat;
 		/* fd was not open? */
 		if (errno == EBADF)
 			return fd;
@@ -1424,13 +1447,14 @@ static void fclose_and_forget(FILE *fp)
 	}
 	fclose(fp);
 }
-static int save_FILEs_on_redirect(int fd)
+static int save_FILEs_on_redirect(int fd, int avoid_fd)
 {
 	struct FILE_list *fl = G.FILE_list;
 	while (fl) {
 		if (fd == fl->fd) {
 			/* We use it only on script files, they are all CLOEXEC */
-			fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC);
+			fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd);
+			debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
 			return 1;
 		}
 		fl = fl->next;
@@ -1443,6 +1467,7 @@ static void restore_redirected_FILEs(void)
 	while (fl) {
 		int should_be = fileno(fl->fp);
 		if (fl->fd != should_be) {
+			debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be);
 			xmove_fd(fl->fd, should_be);
 			fl->fd = should_be;
 		}
@@ -6518,77 +6543,108 @@ static void setup_heredoc(struct redir_struct *redir)
 	wait(NULL); /* wait till child has died */
 }
 
-/* fd: redirect wants this fd to be used (e.g. 3>file).
- * Move all conflicting internally used fds,
- * and remember them so that we can restore them later.
- */
-static int save_fds_on_redirect(int fd, int squirrel[3])
+struct squirrel {
+	int orig_fd;
+	int moved_to;
+	/* moved_to = n: fd was moved to n; restore back to orig_fd after redir */
+	/* moved_to = -1: fd was opened by redirect; close orig_fd after redir */
+};
+
+static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
 {
-	if (squirrel) {
-		/* Handle redirects of fds 0,1,2 */
+	int i = 0;
 
-		/* If we collide with an already moved stdio fd... */
-		if (fd == squirrel[0]) {
-			squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD);
-			return 1;
-		}
-		if (fd == squirrel[1]) {
-			squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD);
-			return 1;
-		}
-		if (fd == squirrel[2]) {
-			squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD);
-			return 1;
-		}
-		/* If we are about to redirect stdio fd, and did not yet move it... */
-		if (fd <= 2 && squirrel[fd] < 0) {
-			/* We avoid taking stdio fds */
-			squirrel[fd] = fcntl(fd, F_DUPFD, 10);
-			if (squirrel[fd] < 0 && errno != EBADF)
+	if (sq) while (sq[i].orig_fd >= 0) {
+		/* If we collide with an already moved fd... */
+		if (fd == sq[i].moved_to) {
+			sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd);
+			debug_printf_redir("redirect_fd %d: already busy, moving to %d\n", fd, sq[i].moved_to);
+			if (sq[i].moved_to < 0) /* what? */
 				xfunc_die();
-			return 0; /* "we did not close fd" */
+			return sq;
+		}
+		if (fd == sq[i].orig_fd) {
+			/* Example: echo Hello >/dev/null 1>&2 */
+			debug_printf_redir("redirect_fd %d: already moved\n", fd);
+			return sq;
 		}
+		i++;
 	}
 
+	sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
+	sq[i].orig_fd = fd;
+	/* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
+	sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd);
+	debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to);
+	if (sq[i].moved_to < 0 && errno != EBADF)
+		xfunc_die();
+	sq[i+1].orig_fd = -1; /* end marker */
+	return sq;
+}
+
+/* fd: redirect wants this fd to be used (e.g. 3>file).
+ * Move all conflicting internally used fds,
+ * and remember them so that we can restore them later.
+ */
+static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
+{
+	if (avoid_fd < 9) /* the important case here is that it can be -1 */
+		avoid_fd = 9;
+
 #if ENABLE_HUSH_INTERACTIVE
 	if (fd != 0 && fd == G.interactive_fd) {
-		G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC);
-		return 1;
+		G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd);
+		debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd);
+		return 1; /* "we closed fd" */
 	}
 #endif
-
 	/* Are we called from setup_redirects(squirrel==NULL)? Two cases:
 	 * (1) Redirect in a forked child. No need to save FILEs' fds,
 	 * we aren't going to use them anymore, ok to trash.
-	 * (2) "exec 3>FILE". Bummer. We can save FILEs' fds,
-	 * but how are we doing to use them?
+	 * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds,
+	 * but how are we doing to restore them?
 	 * "fileno(fd) = new_fd" can't be done.
 	 */
-	if (!squirrel)
+	if (!sqp)
 		return 0;
 
-	return save_FILEs_on_redirect(fd);
+	/* If this one of script's fds? */
+	if (save_FILEs_on_redirect(fd, avoid_fd))
+		return 1; /* yes. "we closed fd" */
+
+	/* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
+	*sqp = add_squirrel(*sqp, fd, avoid_fd);
+	return 0; /* "we did not close fd" */
 }
 
-static void restore_redirects(int squirrel[3])
+static void restore_redirects(struct squirrel *sq)
 {
-	int i, fd;
-	for (i = 0; i <= 2; i++) {
-		fd = squirrel[i];
-		if (fd != -1) {
-			/* We simply die on error */
-			xmove_fd(fd, i);
+
+	if (sq) {
+		int i = 0;
+		while (sq[i].orig_fd >= 0) {
+			if (sq[i].moved_to >= 0) {
+				/* We simply die on error */
+				debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd);
+				xmove_fd(sq[i].moved_to, sq[i].orig_fd);
+			} else {
+				/* cmd1 9>FILE; cmd2_should_see_fd9_closed */
+				debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd);
+				close(sq[i].orig_fd);
+			}
+			i++;
 		}
+		free(sq);
 	}
 
-	/* Moved G.interactive_fd stays on new fd, not doing anything for it */
+	/* If moved, G.interactive_fd stays on new fd, not restoring it */
 
 	restore_redirected_FILEs();
 }
 
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
  * and stderr if they are redirected. */
-static int setup_redirects(struct command *prog, int squirrel[])
+static int setup_redirects(struct command *prog, struct squirrel **sqp)
 {
 	int openfd, mode;
 	struct redir_struct *redir;
@@ -6596,7 +6652,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
 	for (redir = prog->redirects; redir; redir = redir->next) {
 		if (redir->rd_type == REDIRECT_HEREDOC2) {
 			/* "rd_fd<<HERE" case */
-			save_fds_on_redirect(redir->rd_fd, squirrel);
+			save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
 			/* for REDIRECT_HEREDOC2, rd_filename holds _contents_
 			 * of the heredoc */
 			debug_printf_parse("set heredoc '%s'\n",
@@ -6635,7 +6691,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
 		}
 
 		if (openfd != redir->rd_fd) {
-			int closed = save_fds_on_redirect(redir->rd_fd, squirrel);
+			int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp);
 			if (openfd == REDIRFD_CLOSE) {
 				/* "rd_fd >&-" means "close me" */
 				if (!closed) {
@@ -7497,14 +7553,14 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
 static int redirect_and_varexp_helper(char ***new_env_p,
 		struct variable **old_vars_p,
 		struct command *command,
-		int squirrel[3],
+		struct squirrel **sqp,
 		char **argv_expanded)
 {
 	/* setup_redirects acts on file descriptors, not FILEs.
 	 * This is perfect for work that comes after exec().
 	 * Is it really safe for inline use?  Experimentally,
 	 * things seem to work. */
-	int rcode = setup_redirects(command, squirrel);
+	int rcode = setup_redirects(command, sqp);
 	if (rcode == 0) {
 		char **new_env = expand_assignments(command->argv, command->assignment_cnt);
 		*new_env_p = new_env;
@@ -7524,8 +7580,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 	struct command *command;
 	char **argv_expanded;
 	char **argv;
-	/* it is not always needed, but we aim to smaller code */
-	int squirrel[] = { -1, -1, -1 };
+	struct squirrel *squirrel = NULL;
 	int rcode;
 
 	debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
@@ -7582,7 +7637,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		/* { list } */
 		debug_printf("non-subshell group\n");
 		rcode = 1; /* exitcode if redir failed */
-		if (setup_redirects(command, squirrel) == 0) {
+		if (setup_redirects(command, &squirrel) == 0) {
 			debug_printf_exec(": run_list\n");
 			rcode = run_list(command->group) & 0xff;
 		}
@@ -7609,7 +7664,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			/* Ensure redirects take effect (that is, create files).
 			 * Try "a=t >file" */
 #if 0 /* A few cases in testsuite fail with this code. FIXME */
-			rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL);
+			rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL);
 			/* Set shell variables */
 			if (new_env) {
 				argv = new_env;
@@ -7631,7 +7686,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 
 #else /* Older, bigger, but more correct code */
 
-			rcode = setup_redirects(command, squirrel);
+			rcode = setup_redirects(command, &squirrel);
 			restore_redirects(squirrel);
 			/* Set shell variables */
 			if (G_x_mode)
@@ -7694,7 +7749,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 					goto clean_up_and_ret1;
 				}
 			}
-			rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded);
+			rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
 			if (rcode == 0) {
 				if (!funcp) {
 					debug_printf_exec(": builtin '%s' '%s'...\n",
@@ -7723,10 +7778,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			unset_vars(new_env);
 			add_vars(old_vars);
 /* clean_up_and_ret0: */
-
-//FIXME: this restores stdio fds, but does not close other redirects!
-//Example: after "echo TEST 9>/dev/null" fd#9 is not closed!
-//The squirreling code needs rework to remember all fds, not just 0,1,2.
 			restore_redirects(squirrel);
  clean_up_and_ret1:
 			free(argv_expanded);
@@ -7739,7 +7790,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		if (ENABLE_FEATURE_SH_NOFORK) {
 			int n = find_applet_by_name(argv_expanded[0]);
 			if (n >= 0 && APPLET_IS_NOFORK(n)) {
-				rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded);
+				rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
 				if (rcode == 0) {
 					debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
 						argv_expanded[0], argv_expanded[1]);
@@ -8694,7 +8745,7 @@ int hush_main(int argc, char **argv)
 			G_saved_tty_pgrp = 0;
 
 		/* try to dup stdin to high fd#, >= 255 */
-		G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+		G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
 		if (G_interactive_fd < 0) {
 			/* try to dup to any fd */
 			G_interactive_fd = dup(STDIN_FILENO);
@@ -8767,7 +8818,7 @@ int hush_main(int argc, char **argv)
 #elif ENABLE_HUSH_INTERACTIVE
 	/* No job control compiled in, only prompt/line editing */
 	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
-		G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+		G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
 		if (G_interactive_fd < 0) {
 			/* try to dup to any fd */
 			G_interactive_fd = dup(STDIN_FILENO);
diff --git a/shell/hush_test/hush-redir/redir3.right b/shell/hush_test/hush-redir/redir3.right
index fd641a8ea..e3c878b7d 100644
--- a/shell/hush_test/hush-redir/redir3.right
+++ b/shell/hush_test/hush-redir/redir3.right
@@ -1,3 +1,2 @@
 TEST
-./redir3.tests: line 4: 9: Bad file descriptor
-Output to fd#9: 1
+hush: can't duplicate file descriptor: Bad file descriptor
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.right b/shell/hush_test/hush-redir/redir_to_bad_fd.right
index 43b8af293..936911ce5 100644
--- a/shell/hush_test/hush-redir/redir_to_bad_fd.right
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.right
@@ -1,2 +1 @@
-./redir_to_bad_fd.tests: line 2: 10: Bad file descriptor
-OK
+hush: can't duplicate file descriptor: Bad file descriptor
-- 
cgit v1.2.3-55-g6feb


From 840a4355d035efc360eeefe5da8a11e47b3c80d3 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 7 Jul 2017 22:56:02 +0200
Subject: hush: fix "(sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $?"

function                                             old     new   delta
process_wait_result                                  414     426     +12
builtin_wait                                         283     291      +8
run_list                                             974     978      +4
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/0 up/down: 24/0)               Total: 24 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/wait6.right   |  2 ++
 shell/ash_test/ash-misc/wait6.tests   |  6 ++++++
 shell/hush.c                          | 15 ++++++++++-----
 shell/hush_test/hush-misc/wait6.right |  2 ++
 shell/hush_test/hush-misc/wait6.tests |  6 ++++++
 5 files changed, 26 insertions(+), 5 deletions(-)
 create mode 100644 shell/ash_test/ash-misc/wait6.right
 create mode 100755 shell/ash_test/ash-misc/wait6.tests
 create mode 100644 shell/hush_test/hush-misc/wait6.right
 create mode 100755 shell/hush_test/hush-misc/wait6.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/wait6.right b/shell/ash_test/ash-misc/wait6.right
new file mode 100644
index 000000000..12decc137
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.right
@@ -0,0 +1,2 @@
+0
+3
diff --git a/shell/ash_test/ash-misc/wait6.tests b/shell/ash_test/ash-misc/wait6.tests
new file mode 100755
index 000000000..c23713199
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.tests
@@ -0,0 +1,6 @@
+# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
+# It prints 0, then 3:
+(sleep 0; exit 3) & sleep 1
+echo $?
+wait $!
+echo $?
diff --git a/shell/hush.c b/shell/hush.c
index 59bddbfff..df96e6fde 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -832,6 +832,7 @@ struct globals {
 	smallint exiting; /* used to prevent EXIT trap recursion */
 	/* These four support $?, $#, and $1 */
 	smalluint last_exitcode;
+	smalluint last_bg_pid_exitcode;
 #if ENABLE_HUSH_SET
 	/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
 	smalluint global_args_malloced;
@@ -7387,10 +7388,13 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
  found_pi_and_prognum:
 	if (dead) {
 		/* child exited */
-		pi->cmds[i].pid = 0;
-		pi->cmds[i].cmd_exitcode = WEXITSTATUS(status);
+		int rcode = WEXITSTATUS(status);
 		if (WIFSIGNALED(status))
-			pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status);
+			rcode = 128 + WTERMSIG(status);
+		pi->cmds[i].cmd_exitcode = rcode;
+		if (G.last_bg_pid == pi->cmds[i].pid)
+			G.last_bg_pid_exitcode = rcode;
+		pi->cmds[i].pid = 0;
 		pi->alive_cmds--;
 		if (!pi->alive_cmds) {
 			if (G_interactive_fd)
@@ -8215,6 +8219,7 @@ static int run_list(struct pipe *pi)
 #endif
 			/* Last command's pid goes to $! */
 			G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
+			G.last_bg_pid_exitcode = 0;
 			debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
 /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
 			rcode = EXIT_SUCCESS;
@@ -9909,6 +9914,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 		ret = waitpid(pid, &status, WNOHANG);
 		if (ret < 0) {
 			/* No */
+			ret = 127;
 			if (errno == ECHILD) {
 				if (G.last_bg_pid > 0 && pid == G.last_bg_pid) {
 					/* "wait $!" but last bg task has already exited. Try:
@@ -9916,7 +9922,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 					 * In bash it prints exitcode 0, then 3.
 					 * In dash, it is 127.
 					 */
-					/* ret = G.last_bg_pid_exitstatus - FIXME */
+					ret = G.last_bg_pid_exitcode;
 				} else {
 					/* Example: "wait 1". mimic bash message */
 					bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
@@ -9925,7 +9931,6 @@ static int FAST_FUNC builtin_wait(char **argv)
 				/* ??? */
 				bb_perror_msg("wait %s", *argv);
 			}
-			ret = 127;
 			continue; /* bash checks all argv[] */
 		}
 		if (ret == 0) {
diff --git a/shell/hush_test/hush-misc/wait6.right b/shell/hush_test/hush-misc/wait6.right
new file mode 100644
index 000000000..12decc137
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.right
@@ -0,0 +1,2 @@
+0
+3
diff --git a/shell/hush_test/hush-misc/wait6.tests b/shell/hush_test/hush-misc/wait6.tests
new file mode 100755
index 000000000..c23713199
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.tests
@@ -0,0 +1,6 @@
+# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
+# It prints 0, then 3:
+(sleep 0; exit 3) & sleep 1
+echo $?
+wait $!
+echo $?
-- 
cgit v1.2.3-55-g6feb


From 13102634bb38befd80fafb5d2ebd915d7dcf0c5f Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sat, 8 Jul 2017 00:24:32 +0200
Subject: hush: explain why wait5.tests is failing

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index df96e6fde..b131f6095 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7401,6 +7401,10 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 				printf(JOB_STATUS_FORMAT, pi->jobid,
 						"Done", pi->cmdtext);
 			delete_finished_bg_job(pi);
+//bash deletes finished jobs from job table only in interactive mode, after "jobs" cmd,
+//or if pid of a new process matches one of the old ones
+//(see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source).
+//Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash.
 		}
 	} else {
 		/* child stopped */
@@ -9899,6 +9903,15 @@ static int FAST_FUNC builtin_wait(char **argv)
 					ret = job_exited_or_stopped(wait_pipe);
 					if (ret < 0)
 						ret = wait_for_child_or_signal(wait_pipe, 0);
+//bash immediately deletes finished jobs from job table only in interactive mode,
+//we _always_ delete them at once. If we'd start doing that, this (and more)
+//would be necessary to avoid accumulating dead jobs:
+# if 0
+					else {
+						if (!wait_pipe->alive_cmds)
+							delete_finished_bg_job(wait_pipe);
+					}
+# endif
 				}
 				/* else: parse_jobspec() already emitted error msg */
 				continue;
-- 
cgit v1.2.3-55-g6feb


From 1609629a918f0e5c8813a113447347db61be7b74 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 10 Jul 2017 10:00:28 +0200
Subject: hush: rename a few functions

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index b131f6095..3533dfaa4 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7217,7 +7217,7 @@ static const char *get_cmdtext(struct pipe *pi)
 	return pi->cmdtext;
 }
 
-static void insert_bg_job(struct pipe *pi)
+static void insert_job_into_table(struct pipe *pi)
 {
 	struct pipe *job, **jobp;
 	int i;
@@ -7249,7 +7249,7 @@ static void insert_bg_job(struct pipe *pi)
 	G.last_jobid = job->jobid;
 }
 
-static void remove_bg_job(struct pipe *pi)
+static void remove_job_from_table(struct pipe *pi)
 {
 	struct pipe *prev_pipe;
 
@@ -7268,9 +7268,9 @@ static void remove_bg_job(struct pipe *pi)
 }
 
 /* Remove a backgrounded job */
-static void delete_finished_bg_job(struct pipe *pi)
+static void delete_finished_job(struct pipe *pi)
 {
-	remove_bg_job(pi);
+	remove_job_from_table(pi);
 	free_pipe(pi);
 }
 #endif /* JOB */
@@ -7359,7 +7359,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 				if (G_interactive_fd) {
 #if ENABLE_HUSH_JOB
 					if (fg_pipe->alive_cmds != 0)
-						insert_bg_job(fg_pipe);
+						insert_job_into_table(fg_pipe);
 #endif
 					return rcode;
 				}
@@ -7400,7 +7400,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 			if (G_interactive_fd)
 				printf(JOB_STATUS_FORMAT, pi->jobid,
 						"Done", pi->cmdtext);
-			delete_finished_bg_job(pi);
+			delete_finished_job(pi);
 //bash deletes finished jobs from job table only in interactive mode, after "jobs" cmd,
 //or if pid of a new process matches one of the old ones
 //(see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source).
@@ -8219,7 +8219,7 @@ static int run_list(struct pipe *pi)
 			 * I'm NOT treating inner &'s as jobs */
 #if ENABLE_HUSH_JOB
 			if (G.run_list_level == 1)
-				insert_bg_job(pi);
+				insert_job_into_table(pi);
 #endif
 			/* Last command's pid goes to $! */
 			G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
@@ -9702,14 +9702,14 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
 	i = kill(- pi->pgrp, SIGCONT);
 	if (i < 0) {
 		if (errno == ESRCH) {
-			delete_finished_bg_job(pi);
+			delete_finished_job(pi);
 			return EXIT_SUCCESS;
 		}
 		bb_perror_msg("kill (SIGCONT)");
 	}
 
 	if (argv[0][0] == 'f') {
-		remove_bg_job(pi);
+		remove_job_from_table(pi); /* FG job shouldn't be in job table */
 		return checkjobs_and_fg_shell(pi);
 	}
 	return EXIT_SUCCESS;
@@ -9909,7 +9909,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 # if 0
 					else {
 						if (!wait_pipe->alive_cmds)
-							delete_finished_bg_job(wait_pipe);
+							delete_finished_job(wait_pipe);
 					}
 # endif
 				}
-- 
cgit v1.2.3-55-g6feb


From 9e55a156f8a07297f620466e8173040336a7c48c Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 10 Jul 2017 10:01:12 +0200
Subject: hush: simplify insert_job_into_table() a bit

function                                             old     new   delta
done_word                                            767     761      -6
insert_job_into_table                                325     264     -61
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 0/2 up/down: 59/-126)           Total: -67 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 3533dfaa4..8223cdbc5 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -3527,9 +3527,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
 	if (r->flag & FLAG_START) {
 		struct parse_context *old;
 
-		old = xmalloc(sizeof(*old));
+		old = xmemdup(ctx, sizeof(*ctx));
 		debug_printf_parse("push stack %p\n", old);
-		*old = *ctx;   /* physical copy */
 		initialize_context(ctx);
 		ctx->stack = old;
 	} else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
@@ -7222,19 +7221,18 @@ static void insert_job_into_table(struct pipe *pi)
 	struct pipe *job, **jobp;
 	int i;
 
-	/* Linear search for the ID of the job to use */
-	pi->jobid = 1;
-	for (job = G.job_list; job; job = job->next)
-		if (job->jobid >= pi->jobid)
-			pi->jobid = job->jobid + 1;
-
-	/* Add job to the list of running jobs */
+	/* Find the end of the list, and find next job ID to use */
+	i = 0;
 	jobp = &G.job_list;
-	while ((job = *jobp) != NULL)
+	while ((job = *jobp) != NULL) {
+		if (job->jobid > i)
+			i = job->jobid;
 		jobp = &job->next;
-	job = *jobp = xmalloc(sizeof(*job));
+	}
+	pi->jobid = i + 1;
 
-	*job = *pi; /* physical copy */
+	/* Create a new job struct at the end */
+	job = *jobp = xmemdup(pi, sizeof(*pi));
 	job->next = NULL;
 	job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
 	/* Cannot copy entire pi->cmds[] vector! This causes double frees */
@@ -7267,7 +7265,6 @@ static void remove_job_from_table(struct pipe *pi)
 		G.last_jobid = 0;
 }
 
-/* Remove a backgrounded job */
 static void delete_finished_job(struct pipe *pi)
 {
 	remove_job_from_table(pi);
@@ -9904,8 +9901,8 @@ static int FAST_FUNC builtin_wait(char **argv)
 					if (ret < 0)
 						ret = wait_for_child_or_signal(wait_pipe, 0);
 //bash immediately deletes finished jobs from job table only in interactive mode,
-//we _always_ delete them at once. If we'd start doing that, this (and more)
-//would be necessary to avoid accumulating dead jobs:
+//we _always_ delete them at once. If we'd start keeping some dead jobs, this
+//(and more) would be necessary to avoid accumulating dead jobs:
 # if 0
 					else {
 						if (!wait_pipe->alive_cmds)
-- 
cgit v1.2.3-55-g6feb


From b057806a6ad7618b10db062bf6c71d09f71ce421 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 10 Jul 2017 10:33:10 +0200
Subject: hush: add TODO for "set -e"

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 8223cdbc5..89cd47d8f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -49,6 +49,7 @@
  *          [un]alias, command, fc, getopts, newgrp, readonly, times
  *      make complex ${var%...} constructs support optional
  *      make here documents optional
+ *      set -e (some ash testsuite entries use it, want to adopt those)
  *
  * Bash compat TODO:
  *      redirection of stdout+stderr: &> and >&
@@ -8421,6 +8422,22 @@ static int set_mode(int state, char mode, const char *o_opt)
 			G.o_opt[idx] = state;
 			break;
 		}
+/* TODO: set -e
+Exit if pipe, list, or compound command exits with a non-zero status.
+Shell does not exit if failed command is part of condition in
+if/while, part of && or || list except the last command, any command
+in a pipe but the last, or if the command's return value is being
+inverted with !. If a compound command other than a subshell returns a
+non-zero status because a command failed while -e was being ignored, the
+shell does not exit. A trap on ERR, if set, is executed before the shell
+exits [ERR is a bashism].
+
+If a compound command or function executes in a context where -e is
+ignored, none of the commands executed within are affected by the -e
+setting. If a compound command or function sets -e while executing in a
+context where -e is ignored, that setting does not have any effect until
+the compound command or the command containing the function call completes.
+*/
 	default:
 		return EXIT_FAILURE;
 	}
-- 
cgit v1.2.3-55-g6feb


From 9fda609a60506f4c1f73f0034cafd2b3434f4df3 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 14 Jul 2017 13:36:48 +0200
Subject: hush: add support for "set -e"

function                                             old     new   delta
run_list                                             978    1046     +68
o_opt_strings                                         24      32      +8
reset_traps_to_defaults                              136     142      +6
pick_sighandler                                       57      60      +3
packed_usage                                       31772   31770      -2
hush_main                                            983     961     -22
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/2 up/down: 85/-24)             Total: 61 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                               | 66 ++++++++++++++++++++----------
 shell/hush_test/hush-misc/errexit1.right   |  1 +
 shell/hush_test/hush-misc/errexit1.tests   |  5 +++
 shell/hush_test/hush-signals/signal8.right |  3 ++
 shell/hush_test/hush-signals/signal8.tests | 18 ++++++++
 shell/hush_test/hush-signals/signal9.right |  3 ++
 shell/hush_test/hush-signals/signal9.tests | 21 ++++++++++
 7 files changed, 96 insertions(+), 21 deletions(-)
 create mode 100644 shell/hush_test/hush-misc/errexit1.right
 create mode 100755 shell/hush_test/hush-misc/errexit1.tests
 create mode 100644 shell/hush_test/hush-signals/signal8.right
 create mode 100755 shell/hush_test/hush-signals/signal8.tests
 create mode 100644 shell/hush_test/hush-signals/signal9.right
 create mode 100755 shell/hush_test/hush-signals/signal9.tests

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 89cd47d8f..553c8e64a 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -49,7 +49,6 @@
  *          [un]alias, command, fc, getopts, newgrp, readonly, times
  *      make complex ${var%...} constructs support optional
  *      make here documents optional
- *      set -e (some ash testsuite entries use it, want to adopt those)
  *
  * Bash compat TODO:
  *      redirection of stdout+stderr: &> and >&
@@ -286,7 +285,7 @@
  * therefore we don't show them either.
  */
 //usage:#define hush_trivial_usage
-//usage:	"[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:	"[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
 //usage:#define hush_full_usage "\n\n"
 //usage:	"Unix shell interpreter"
 
@@ -747,6 +746,7 @@ struct function {
 static const char o_opt_strings[] ALIGN1 =
 	"pipefail\0"
 	"noexec\0"
+	"errexit\0"
 #if ENABLE_HUSH_MODE_X
 	"xtrace\0"
 #endif
@@ -754,6 +754,7 @@ static const char o_opt_strings[] ALIGN1 =
 enum {
 	OPT_O_PIPEFAIL,
 	OPT_O_NOEXEC,
+	OPT_O_ERREXIT,
 #if ENABLE_HUSH_MODE_X
 	OPT_O_XTRACE,
 #endif
@@ -810,6 +811,25 @@ struct globals {
 #else
 # define G_saved_tty_pgrp 0
 #endif
+	/* How deeply are we in context where "set -e" is ignored */
+	int errexit_depth;
+	/* "set -e" rules (do we follow them correctly?):
+	 * Exit if pipe, list, or compound command exits with a non-zero status.
+	 * Shell does not exit if failed command is part of condition in
+	 * if/while, part of && or || list except the last command, any command
+	 * in a pipe but the last, or if the command's return value is being
+	 * inverted with !. If a compound command other than a subshell returns a
+	 * non-zero status because a command failed while -e was being ignored, the
+	 * shell does not exit. A trap on ERR, if set, is executed before the shell
+	 * exits [ERR is a bashism].
+	 *
+	 * If a compound command or function executes in a context where -e is
+	 * ignored, none of the commands executed within are affected by the -e
+	 * setting. If a compound command or function sets -e while executing in a
+	 * context where -e is ignored, that setting does not have any effect until
+	 * the compound command or the command containing the function call completes.
+	 */
+
 	char o_opt[NUM_OPT_O];
 #if ENABLE_HUSH_MODE_X
 # define G_x_mode (G.o_opt[OPT_O_XTRACE])
@@ -5159,7 +5179,7 @@ static struct pipe *parse_stream(char **pstring,
 			 * and it will match } earlier (not here). */
 			syntax_error_unexpected_ch(ch);
 			G.last_exitcode = 2;
-			goto parse_error1;
+			goto parse_error2;
 		default:
 			if (HUSH_DEBUG)
 				bb_error_msg_and_die("BUG: unexpected %c\n", ch);
@@ -5168,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring,
 
  parse_error:
 	G.last_exitcode = 1;
- parse_error1:
+ parse_error2:
 	{
 		struct parse_context *pctx;
 		IF_HAS_KEYWORDS(struct parse_context *p2;)
@@ -8021,6 +8041,7 @@ static int run_list(struct pipe *pi)
 	/* Go through list of pipes, (maybe) executing them. */
 	for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
 		int r;
+		int sv_errexit_depth;
 
 		if (G.flag_SIGINT)
 			break;
@@ -8030,6 +8051,13 @@ static int run_list(struct pipe *pi)
 		IF_HAS_KEYWORDS(rword = pi->res_word;)
 		debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
 				rword, cond_code, last_rword);
+
+		sv_errexit_depth = G.errexit_depth;
+		if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||)
+		    pi->followup != PIPE_SEQ
+		) {
+			G.errexit_depth++;
+		}
 #if ENABLE_HUSH_LOOPS
 		if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
 		 && loop_top == NULL /* avoid bumping G.depth_of_loop twice */
@@ -8243,6 +8271,14 @@ static int run_list(struct pipe *pi)
 			check_and_run_traps();
 		}
 
+		/* Handle "set -e" */
+		if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) {
+			debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth);
+			if (G.errexit_depth == 0)
+				hush_exit(rcode);
+		}
+		G.errexit_depth = sv_errexit_depth;
+
 		/* Analyze how result affects subsequent commands */
 #if ENABLE_HUSH_IF
 		if (rword == RES_IF || rword == RES_ELIF)
@@ -8422,22 +8458,9 @@ static int set_mode(int state, char mode, const char *o_opt)
 			G.o_opt[idx] = state;
 			break;
 		}
-/* TODO: set -e
-Exit if pipe, list, or compound command exits with a non-zero status.
-Shell does not exit if failed command is part of condition in
-if/while, part of && or || list except the last command, any command
-in a pipe but the last, or if the command's return value is being
-inverted with !. If a compound command other than a subshell returns a
-non-zero status because a command failed while -e was being ignored, the
-shell does not exit. A trap on ERR, if set, is executed before the shell
-exits [ERR is a bashism].
-
-If a compound command or function executes in a context where -e is
-ignored, none of the commands executed within are affected by the -e
-setting. If a compound command or function sets -e while executing in a
-context where -e is ignored, that setting does not have any effect until
-the compound command or the command containing the function call completes.
-*/
+	case 'e':
+		G.o_opt[OPT_O_ERREXIT] = state;
+		break;
 	default:
 		return EXIT_FAILURE;
 	}
@@ -8564,7 +8587,7 @@ int hush_main(int argc, char **argv)
 	flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
 	builtin_argc = 0;
 	while (1) {
-		opt = getopt(argc, argv, "+c:xinsl"
+		opt = getopt(argc, argv, "+c:exinsl"
 #if !BB_MMU
 				"<:$:R:V:"
 # if ENABLE_HUSH_FUNCTIONS
@@ -8682,6 +8705,7 @@ int hush_main(int argc, char **argv)
 #endif
 		case 'n':
 		case 'x':
+		case 'e':
 			if (set_mode(1, opt, NULL) == 0) /* no error */
 				break;
 		default:
diff --git a/shell/hush_test/hush-misc/errexit1.right b/shell/hush_test/hush-misc/errexit1.right
new file mode 100644
index 000000000..d86bac9de
--- /dev/null
+++ b/shell/hush_test/hush-misc/errexit1.right
@@ -0,0 +1 @@
+OK
diff --git a/shell/hush_test/hush-misc/errexit1.tests b/shell/hush_test/hush-misc/errexit1.tests
new file mode 100755
index 000000000..7b4a15634
--- /dev/null
+++ b/shell/hush_test/hush-misc/errexit1.tests
@@ -0,0 +1,5 @@
+set -e
+(true)
+echo OK
+(false)
+echo FAIL
diff --git a/shell/hush_test/hush-signals/signal8.right b/shell/hush_test/hush-signals/signal8.right
new file mode 100644
index 000000000..39572f30e
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal8.right
@@ -0,0 +1,3 @@
+Removing traps
+End of exit_func
+Done: 0
diff --git a/shell/hush_test/hush-signals/signal8.tests b/shell/hush_test/hush-signals/signal8.tests
new file mode 100755
index 000000000..731af7477
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal8.tests
@@ -0,0 +1,18 @@
+"$THIS_SH" -c '
+exit_func() {
+    echo "Removing traps"
+    trap - EXIT TERM INT
+    echo "End of exit_func"
+}
+set -e
+trap exit_func EXIT TERM INT
+sleep 2
+exit 77
+' &
+
+sleep 1
+# BUG: ash kills -PGRP, but in non-interactive shell we do not create pgrps!
+# In this case, bash kills by PID, not PGRP.
+kill -TERM %1
+wait
+echo Done: $?
diff --git a/shell/hush_test/hush-signals/signal9.right b/shell/hush_test/hush-signals/signal9.right
new file mode 100644
index 000000000..39572f30e
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal9.right
@@ -0,0 +1,3 @@
+Removing traps
+End of exit_func
+Done: 0
diff --git a/shell/hush_test/hush-signals/signal9.tests b/shell/hush_test/hush-signals/signal9.tests
new file mode 100755
index 000000000..18e71012b
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal9.tests
@@ -0,0 +1,21 @@
+# Note: the inner script is a test which checks for a different bug
+# (ordering between INT handler and exit on "set -e"),
+# but so far I did not figure out how to simulate it non-interactively.
+
+"$THIS_SH" -c '
+exit_func() {
+    echo "Removing traps"
+    trap - EXIT TERM INT
+    echo "End of exit_func"
+}
+set -e
+trap exit_func EXIT TERM INT
+sleep 2
+exit 77
+' &
+
+child=$!
+sleep 1
+kill -TERM $child
+wait
+echo Done: $?
-- 
cgit v1.2.3-55-g6feb


From 0c5657e9119eb52263e83e9b55394a8f43f4e928 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 14 Jul 2017 19:27:03 +0200
Subject: hush: remove superfluous comparison

function                                             old     new   delta
builtin_wait                                         291     285      -6

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 553c8e64a..af5c26090 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9967,7 +9967,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 			/* No */
 			ret = 127;
 			if (errno == ECHILD) {
-				if (G.last_bg_pid > 0 && pid == G.last_bg_pid) {
+				if (pid == G.last_bg_pid) {
 					/* "wait $!" but last bg task has already exited. Try:
 					 * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $?
 					 * In bash it prints exitcode 0, then 3.
-- 
cgit v1.2.3-55-g6feb


From 2ed74e25d354e6958dc86a21aa32c2dacb809bf0 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 14 Jul 2017 19:58:46 +0200
Subject: hush: make "wait %1" work even if the job is dead

Example script:

	sleep 1 | (sleep 1;exit 3) &
	sleep 2
	echo Zero:$?
	wait %1
	echo Three:$?

function                                             old     new   delta
clean_up_last_dead_job                                 -      24     +24
process_wait_result                                  426     447     +21
builtin_wait                                         285     293      +8
insert_job_into_table                                264     269      +5
builtin_jobs                                          68      73      +5
remove_job_from_table                                 59      57      -2
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 4/1 up/down: 63/-2)              Total: 61 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 91 ++++++++++++++++++++++++++++++++++--------------------------
 1 file changed, 52 insertions(+), 39 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index af5c26090..b76351fde 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7237,11 +7237,42 @@ static const char *get_cmdtext(struct pipe *pi)
 	return pi->cmdtext;
 }
 
+static void remove_job_from_table(struct pipe *pi)
+{
+	struct pipe *prev_pipe;
+
+	if (pi == G.job_list) {
+		G.job_list = pi->next;
+	} else {
+		prev_pipe = G.job_list;
+		while (prev_pipe->next != pi)
+			prev_pipe = prev_pipe->next;
+		prev_pipe->next = pi->next;
+	}
+	G.last_jobid = 0;
+	if (G.job_list)
+		G.last_jobid = G.job_list->jobid;
+}
+
+static void delete_finished_job(struct pipe *pi)
+{
+	remove_job_from_table(pi);
+	free_pipe(pi);
+}
+
+static void clean_up_last_dead_job(void)
+{
+	if (G.job_list && !G.job_list->alive_cmds)
+		delete_finished_job(G.job_list);
+}
+
 static void insert_job_into_table(struct pipe *pi)
 {
 	struct pipe *job, **jobp;
 	int i;
 
+	clean_up_last_dead_job();
+
 	/* Find the end of the list, and find next job ID to use */
 	i = 0;
 	jobp = &G.job_list;
@@ -7267,30 +7298,6 @@ static void insert_job_into_table(struct pipe *pi)
 		printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext);
 	G.last_jobid = job->jobid;
 }
-
-static void remove_job_from_table(struct pipe *pi)
-{
-	struct pipe *prev_pipe;
-
-	if (pi == G.job_list) {
-		G.job_list = pi->next;
-	} else {
-		prev_pipe = G.job_list;
-		while (prev_pipe->next != pi)
-			prev_pipe = prev_pipe->next;
-		prev_pipe->next = pi->next;
-	}
-	if (G.job_list)
-		G.last_jobid = G.job_list->jobid;
-	else
-		G.last_jobid = 0;
-}
-
-static void delete_finished_job(struct pipe *pi)
-{
-	remove_job_from_table(pi);
-	free_pipe(pi);
-}
 #endif /* JOB */
 
 static int job_exited_or_stopped(struct pipe *pi)
@@ -7415,14 +7422,22 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 		pi->cmds[i].pid = 0;
 		pi->alive_cmds--;
 		if (!pi->alive_cmds) {
-			if (G_interactive_fd)
+			if (G_interactive_fd) {
 				printf(JOB_STATUS_FORMAT, pi->jobid,
 						"Done", pi->cmdtext);
-			delete_finished_job(pi);
-//bash deletes finished jobs from job table only in interactive mode, after "jobs" cmd,
-//or if pid of a new process matches one of the old ones
-//(see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source).
-//Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash.
+				delete_finished_job(pi);
+			} else {
+/*
+ * bash deletes finished jobs from job table only in interactive mode,
+ * after "jobs" cmd, or if pid of a new process matches one of the old ones
+ * (see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source).
+ * Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash.
+ * We only retain one "dead" job, if it's the single job on the list.
+ * This covers most of real-world scenarios where this is useful.
+ */
+				if (pi != G.job_list)
+					delete_finished_job(pi);
+			}
 		}
 	} else {
 		/* child stopped */
@@ -9696,6 +9711,9 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
 
 		printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
 	}
+
+	clean_up_last_dead_job();
+
 	return EXIT_SUCCESS;
 }
 
@@ -9939,17 +9957,12 @@ static int FAST_FUNC builtin_wait(char **argv)
 				wait_pipe = parse_jobspec(*argv);
 				if (wait_pipe) {
 					ret = job_exited_or_stopped(wait_pipe);
-					if (ret < 0)
+					if (ret < 0) {
 						ret = wait_for_child_or_signal(wait_pipe, 0);
-//bash immediately deletes finished jobs from job table only in interactive mode,
-//we _always_ delete them at once. If we'd start keeping some dead jobs, this
-//(and more) would be necessary to avoid accumulating dead jobs:
-# if 0
-					else {
-						if (!wait_pipe->alive_cmds)
-							delete_finished_job(wait_pipe);
+					} else {
+						/* waiting on "last dead job" removes it */
+						clean_up_last_dead_job();
 					}
-# endif
 				}
 				/* else: parse_jobspec() already emitted error msg */
 				continue;
-- 
cgit v1.2.3-55-g6feb


From 9d4dc84a769e3e45e420fbef7c7bb2acbbdccd8e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 14 Jul 2017 22:25:58 +0200
Subject: ash: protect WIFSTOPPED use with #if JOBS

This change fixes the build in setups where there are
no headers defining WIFSTOPPED and WSTOPSIG (where JOBS has to be
set to 0).

This partially reverts 4700fb5be (ash: make dowait() a bit more
readable. Logic is unchanged, 2015-10-09).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 8c2098dd9..b0c7dac54 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -4022,15 +4022,19 @@ sprint_status48(char *s, int status, int sigonly)
 
 	col = 0;
 	if (!WIFEXITED(status)) {
-		if (JOBS && WIFSTOPPED(status))
+#if JOBS
+		if (WIFSTOPPED(status))
 			st = WSTOPSIG(status);
 		else
+#endif
 			st = WTERMSIG(status);
 		if (sigonly) {
 			if (st == SIGINT || st == SIGPIPE)
 				goto out;
-			if (JOBS && WIFSTOPPED(status))
+#if JOBS
+			if (WIFSTOPPED(status))
 				goto out;
+#endif
 		}
 		st &= 0x7f;
 //TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
@@ -4182,8 +4186,10 @@ dowait(int block, struct job *job)
 		goto out;
 	}
 	/* The process wasn't found in job list */
-	if (JOBS && !WIFSTOPPED(status))
+#if JOBS
+	if (!WIFSTOPPED(status))
 		jobless--;
+#endif
  out:
 	INT_ON;
 
-- 
cgit v1.2.3-55-g6feb


From ee553b929c81ae0051abfd54984aa0537f767d89 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sat, 15 Jul 2017 22:51:55 +0200
Subject: hush: fix and_or_and_backgrounding.tests failure

function                                             old     new   delta
done_pipe                                            133     218     +85

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 .../ash-parsing/and_or_and_backgrounding.right     |  4 +++
 .../ash-parsing/and_or_and_backgrounding.tests     | 31 ++++++++++++++++++
 shell/hush.c                                       | 37 +++++++++++++++++++++-
 .../hush-bugs/and_or_and_backgrounding.right       |  4 ---
 .../hush-bugs/and_or_and_backgrounding.tests       | 31 ------------------
 .../hush-parsing/and_or_and_backgrounding.right    |  4 +++
 .../hush-parsing/and_or_and_backgrounding.tests    | 31 ++++++++++++++++++
 7 files changed, 106 insertions(+), 36 deletions(-)
 create mode 100644 shell/ash_test/ash-parsing/and_or_and_backgrounding.right
 create mode 100755 shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
 delete mode 100644 shell/hush_test/hush-bugs/and_or_and_backgrounding.right
 delete mode 100755 shell/hush_test/hush-bugs/and_or_and_backgrounding.tests
 create mode 100644 shell/hush_test/hush-parsing/and_or_and_backgrounding.right
 create mode 100755 shell/hush_test/hush-parsing/and_or_and_backgrounding.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-parsing/and_or_and_backgrounding.right b/shell/ash_test/ash-parsing/and_or_and_backgrounding.right
new file mode 100644
index 000000000..90ce63e01
--- /dev/null
+++ b/shell/ash_test/ash-parsing/and_or_and_backgrounding.right
@@ -0,0 +1,4 @@
+First
+Second
+Third
+Done
diff --git a/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
new file mode 100755
index 000000000..05acfb863
--- /dev/null
+++ b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
@@ -0,0 +1,31 @@
+# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
+# According to this doc, && || have higher precedence than ; &.
+# See example below.
+# Precedence of ; is not a problem in practice. Precedence of & is.
+#
+#http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+#
+#2.9.3 Lists
+#
+#An AND-OR list is a sequence of one or more pipelines separated by
+#the operators "&&" and "||" .
+#
+#A list is a sequence of one or more AND-OR lists separated by the operators
+#';' and '&' and optionally terminated by ';', '&', or <newline>.
+#
+#The operators "&&" and "||" shall have equal precedence and shall be
+#evaluated with left associativity. For example, both of the following
+#commands write solely bar to standard output:
+#
+#    false && echo foo || echo bar
+#    true || echo foo && echo bar
+#
+#A ';' or <newline> terminator shall cause the preceding AND-OR list
+#to be executed sequentially; an '&' shall cause asynchronous execution
+#of the preceding AND-OR list.
+
+echo First && sleep 0.2 && echo Third &
+sleep 0.1
+echo Second
+wait
+echo Done
diff --git a/shell/hush.c b/shell/hush.c
index b76351fde..2a734f3de 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -3373,12 +3373,47 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
 	debug_printf_parse("done_pipe entered, followup %d\n", type);
 	/* Close previous command */
 	not_null = done_command(ctx);
-	ctx->pipe->followup = type;
 #if HAS_KEYWORDS
 	ctx->pipe->pi_inverted = ctx->ctx_inverted;
 	ctx->ctx_inverted = 0;
 	ctx->pipe->res_word = ctx->ctx_res_w;
 #endif
+	if (type != PIPE_BG || ctx->list_head == ctx->pipe) {
+ no_conv:
+		ctx->pipe->followup = type;
+	} else {
+		/* Necessary since && and || have more precedence than &:
+		 * "cmd1 && cmd2 &" must spawn both cmds, not only cmd2,
+		 * in a backgrounded subshell.
+		 */
+		struct pipe *pi;
+		struct command *command;
+
+		/* Is this actually the case? */
+		pi = ctx->list_head;
+		while (pi != ctx->pipe) {
+			if (pi->followup != PIPE_AND && pi->followup != PIPE_OR)
+				goto no_conv;
+			pi = pi->next;
+		}
+
+		debug_printf_parse("BG with more than one pipe, converting to { p1 &&...pN; } &\n");
+		pi->followup = PIPE_SEQ; /* close pN _not_ with "&"! */
+		pi = xzalloc(sizeof(*pi));
+		pi->followup = PIPE_BG;
+		pi->num_cmds = 1;
+		pi->cmds = xzalloc(sizeof(pi->cmds[0]));
+		command = &pi->cmds[0];
+		if (CMD_NORMAL != 0) /* "if xzalloc didn't do that already" */
+			command->cmd_type = CMD_NORMAL;
+		command->group = ctx->list_head;
+#if !BB_MMU
+//TODO: is this correct?!
+		command->group_as_string = xstrdup(ctx->as_string.data);
+#endif
+		/* Replace all pipes in ctx with one newly created */
+		ctx->list_head = ctx->pipe = pi;
+	}
 
 	/* Without this check, even just <enter> on command line generates
 	 * tree of three NOPs (!). Which is harmless but annoying.
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.right b/shell/hush_test/hush-bugs/and_or_and_backgrounding.right
deleted file mode 100644
index 90ce63e01..000000000
--- a/shell/hush_test/hush-bugs/and_or_and_backgrounding.right
+++ /dev/null
@@ -1,4 +0,0 @@
-First
-Second
-Third
-Done
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests b/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests
deleted file mode 100755
index 05acfb863..000000000
--- a/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests
+++ /dev/null
@@ -1,31 +0,0 @@
-# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
-# According to this doc, && || have higher precedence than ; &.
-# See example below.
-# Precedence of ; is not a problem in practice. Precedence of & is.
-#
-#http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
-#
-#2.9.3 Lists
-#
-#An AND-OR list is a sequence of one or more pipelines separated by
-#the operators "&&" and "||" .
-#
-#A list is a sequence of one or more AND-OR lists separated by the operators
-#';' and '&' and optionally terminated by ';', '&', or <newline>.
-#
-#The operators "&&" and "||" shall have equal precedence and shall be
-#evaluated with left associativity. For example, both of the following
-#commands write solely bar to standard output:
-#
-#    false && echo foo || echo bar
-#    true || echo foo && echo bar
-#
-#A ';' or <newline> terminator shall cause the preceding AND-OR list
-#to be executed sequentially; an '&' shall cause asynchronous execution
-#of the preceding AND-OR list.
-
-echo First && sleep 0.2 && echo Third &
-sleep 0.1
-echo Second
-wait
-echo Done
diff --git a/shell/hush_test/hush-parsing/and_or_and_backgrounding.right b/shell/hush_test/hush-parsing/and_or_and_backgrounding.right
new file mode 100644
index 000000000..90ce63e01
--- /dev/null
+++ b/shell/hush_test/hush-parsing/and_or_and_backgrounding.right
@@ -0,0 +1,4 @@
+First
+Second
+Third
+Done
diff --git a/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
new file mode 100755
index 000000000..05acfb863
--- /dev/null
+++ b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
@@ -0,0 +1,31 @@
+# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
+# According to this doc, && || have higher precedence than ; &.
+# See example below.
+# Precedence of ; is not a problem in practice. Precedence of & is.
+#
+#http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+#
+#2.9.3 Lists
+#
+#An AND-OR list is a sequence of one or more pipelines separated by
+#the operators "&&" and "||" .
+#
+#A list is a sequence of one or more AND-OR lists separated by the operators
+#';' and '&' and optionally terminated by ';', '&', or <newline>.
+#
+#The operators "&&" and "||" shall have equal precedence and shall be
+#evaluated with left associativity. For example, both of the following
+#commands write solely bar to standard output:
+#
+#    false && echo foo || echo bar
+#    true || echo foo && echo bar
+#
+#A ';' or <newline> terminator shall cause the preceding AND-OR list
+#to be executed sequentially; an '&' shall cause asynchronous execution
+#of the preceding AND-OR list.
+
+echo First && sleep 0.2 && echo Third &
+sleep 0.1
+echo Second
+wait
+echo Done
-- 
cgit v1.2.3-55-g6feb


From 9f904a22ffcaa517a0836a635a4a676b6b93c8fe Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sat, 15 Jul 2017 22:54:46 +0200
Subject: shell: and_or_and_backgrounding.tests is no longer "UNFIXED BUG"

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-parsing/and_or_and_backgrounding.tests   | 1 -
 shell/hush_test/hush-parsing/and_or_and_backgrounding.tests | 1 -
 2 files changed, 2 deletions(-)

(limited to 'shell')

diff --git a/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
index 05acfb863..204d44490 100755
--- a/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
+++ b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
@@ -1,4 +1,3 @@
-# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
 # According to this doc, && || have higher precedence than ; &.
 # See example below.
 # Precedence of ; is not a problem in practice. Precedence of & is.
diff --git a/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
index 05acfb863..204d44490 100755
--- a/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
+++ b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
@@ -1,4 +1,3 @@
-# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
 # According to this doc, && || have higher precedence than ; &.
 # See example below.
 # Precedence of ; is not a problem in practice. Precedence of & is.
-- 
cgit v1.2.3-55-g6feb


From b24e55da84093dd6bf6dff79627e32459c3da071 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Sun, 16 Jul 2017 20:29:35 +0200
Subject: hush: fix "cmd1 && cmd2 &" handling on NOMMU

function                                             old     new   delta
done_pipe                                            234     238      +4

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 2a734f3de..8d9292ed1 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -3378,18 +3378,15 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
 	ctx->ctx_inverted = 0;
 	ctx->pipe->res_word = ctx->ctx_res_w;
 #endif
-	if (type != PIPE_BG || ctx->list_head == ctx->pipe) {
- no_conv:
-		ctx->pipe->followup = type;
-	} else {
-		/* Necessary since && and || have more precedence than &:
+	if (type == PIPE_BG && ctx->list_head != ctx->pipe) {
+		/* Necessary since && and || have precedence over &:
 		 * "cmd1 && cmd2 &" must spawn both cmds, not only cmd2,
 		 * in a backgrounded subshell.
 		 */
 		struct pipe *pi;
 		struct command *command;
 
-		/* Is this actually the case? */
+		/* Is this actually this construct, all pipes end with && or ||? */
 		pi = ctx->list_head;
 		while (pi != ctx->pipe) {
 			if (pi->followup != PIPE_AND && pi->followup != PIPE_OR)
@@ -3408,11 +3405,16 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
 			command->cmd_type = CMD_NORMAL;
 		command->group = ctx->list_head;
 #if !BB_MMU
-//TODO: is this correct?!
-		command->group_as_string = xstrdup(ctx->as_string.data);
+		command->group_as_string = xstrndup(
+			    ctx->as_string.data,
+			    ctx->as_string.length - 1 /* do not copy last char, "&" */
+		);
 #endif
 		/* Replace all pipes in ctx with one newly created */
 		ctx->list_head = ctx->pipe = pi;
+	} else {
+ no_conv:
+		ctx->pipe->followup = type;
 	}
 
 	/* Without this check, even just <enter> on command line generates
@@ -4855,7 +4857,9 @@ static struct pipe *parse_stream(char **pstring,
 					 * Really, ask yourself, why
 					 * "cmd && <newline>" doesn't start
 					 * cmd but waits for more input?
-					 * No reason...)
+					 * The only reason is that it might be
+					 * a "cmd1 && <nl> cmd2 &" construct,
+					 * cmd1 may need to run in BG).
 					 */
 					struct pipe *pi = ctx.list_head;
 					if (pi->num_cmds != 0       /* check #1 */
-- 
cgit v1.2.3-55-g6feb


From 203fd7bc66b869e5022ad33d26065e69eaaaf66b Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 16:13:35 +0200
Subject: shells: expand TODO comments, no code changes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c  |  1 +
 shell/hush.c | 31 ++++++++++++++++++++++++++-----
 2 files changed, 27 insertions(+), 5 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index b0c7dac54..987d618c6 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6680,6 +6680,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 			if (*loc++ == ':') {
 				len = number(loc);
 			}
+//TODO: number() chokes on "-n". In bash, LEN=-n means strlen()-n
 		}
 		if (pos < 0) {
 			/* ${VAR:$((-n)):l} starts n chars from the end */
diff --git a/shell/hush.c b/shell/hush.c
index 8d9292ed1..fd2a3d0f5 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -41,14 +41,29 @@
  *
  * TODOs:
  *      grep for "TODO" and fix (some of them are easy)
+ *      make complex ${var%...} constructs support optional
+ *      make here documents optional
  *      special variables (done: PWD, PPID, RANDOM)
+ *      follow IFS rules more precisely, including update semantics
  *      tilde expansion
  *      aliases
- *      follow IFS rules more precisely, including update semantics
  *      builtins mandated by standards we don't support:
- *          [un]alias, command, fc, getopts, newgrp, readonly, times
- *      make complex ${var%...} constructs support optional
- *      make here documents optional
+ *          [un]alias, command, fc, getopts, readonly, times:
+ *          command -v CMD: print "/path/to/CMD"
+ *              prints "CMD" for builtins
+ *              prints "alias ALIAS='EXPANSION'" for aliases
+ *              prints nothing and sets $? to 1 if not found
+ *          command -V CMD: print "CMD is /path/CMD|a shell builtin|etc"
+ *          command [-p] CMD: run CMD, even if a function CMD also exists
+ *              (can use this to override standalone shell as well)
+ *              -p: use default $PATH
+ *          readonly VAR[=VAL]...: make VARs readonly
+ *          readonly [-p]: list all such VARs (-p has no effect in bash)
+ *          getopts: getopt() for shells
+ *          times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime
+ *          fc -l[nr] [BEG] [END]: list range of commands in history
+ *          fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
+ *          fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
  *
  * Bash compat TODO:
  *      redirection of stdout+stderr: &> and >&
@@ -64,8 +79,13 @@
  *          The EXPR is evaluated according to ARITHMETIC EVALUATION.
  *          This is exactly equivalent to let "EXPR".
  *      $[EXPR]: synonym for $((EXPR))
+ *      indirect expansion: ${!VAR}
+ *      substring op on @: ${@:n:m}
  *
  * Won't do:
+ *      Some builtins mandated by standards:
+ *          newgrp [GRP]: not a builtin in bash but a suid binary
+ *              which spawns a new shell with new group ID
  *      In bash, export builtin is special, its arguments are assignments
  *          and therefore expansion of them should be "one-word" expansion:
  *              $ export i=`echo 'a  b'` # export has one arg: "i=a  b"
@@ -5703,7 +5723,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 			if (errmsg)
 				goto arith_err;
 			debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
-			if (len >= 0) { /* bash compat: len < 0 is illegal */
+			if (len >= 0) {
 				if (beg < 0) {
 					/* negative beg counts from the end */
 					beg = (arith_t)strlen(val) + beg;
@@ -5723,6 +5743,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				}
 				debug_printf_varexp("val:'%s'\n", val);
 			} else
+//TODO: in bash, len=-n means strlen()-n
 #endif /* HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH */
 			{
 				die_if_script("malformed ${%s:...}", var);
-- 
cgit v1.2.3-55-g6feb


From e32b6503e75d5bcbf8ffff69cafb09523ff2b482 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 16:46:57 +0200
Subject: hush: support ${VAR:N:-M}

function                                             old     new   delta
expand_one_var                                      1602    1615     +13
builtin_type                                         114     116      +2

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                               | 48 ++++++++++++++++--------------
 shell/hush_test/hush-vars/var_bash1b.right | 23 ++++++++++++++
 shell/hush_test/hush-vars/var_bash1b.tests | 24 +++++++++++++++
 3 files changed, 72 insertions(+), 23 deletions(-)
 create mode 100644 shell/hush_test/hush-vars/var_bash1b.right
 create mode 100755 shell/hush_test/hush-vars/var_bash1b.tests

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index fd2a3d0f5..836f3b83c 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5723,32 +5723,34 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 			if (errmsg)
 				goto arith_err;
 			debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
-			if (len >= 0) {
-				if (beg < 0) {
-					/* negative beg counts from the end */
-					beg = (arith_t)strlen(val) + beg;
-					if (beg < 0) /* ${v: -999999} is "" */
-						beg = len = 0;
-				}
-				debug_printf_varexp("from val:'%s'\n", val);
-				if (len == 0 || !val || beg >= strlen(val)) {
+			if (beg < 0) {
+				/* negative beg counts from the end */
+				beg = (arith_t)strlen(val) + beg;
+				if (beg < 0) /* ${v: -999999} is "" */
+					beg = len = 0;
+			}
+			debug_printf_varexp("from val:'%s'\n", val);
+			if (len < 0) {
+				/* in bash, len=-n means strlen()-n */
+				len = (arith_t)strlen(val) - beg + len;
+				if (len < 0) /* bash compat */
+					die_if_script("%s: substring expression < 0", var);
+			}
+			if (len == 0 || !val || beg >= strlen(val)) {
  arith_err:
-					val = NULL;
-				} else {
-					/* Paranoia. What if user entered 9999999999999
-					 * which fits in arith_t but not int? */
-					if (len >= INT_MAX)
-						len = INT_MAX;
-					val = to_be_freed = xstrndup(val + beg, len);
-				}
-				debug_printf_varexp("val:'%s'\n", val);
-			} else
-//TODO: in bash, len=-n means strlen()-n
-#endif /* HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH */
-			{
-				die_if_script("malformed ${%s:...}", var);
 				val = NULL;
+			} else {
+				/* Paranoia. What if user entered 9999999999999
+				 * which fits in arith_t but not int? */
+				if (len >= INT_MAX)
+					len = INT_MAX;
+				val = to_be_freed = xstrndup(val + beg, len);
 			}
+			debug_printf_varexp("val:'%s'\n", val);
+#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */
+			die_if_script("malformed ${%s:...}", var);
+			val = NULL;
+#endif
 		} else { /* one of "-=+?" */
 			/* Standard-mandated substitution ops:
 			 * ${var?word} - indicate error if unset
diff --git a/shell/hush_test/hush-vars/var_bash1b.right b/shell/hush_test/hush-vars/var_bash1b.right
new file mode 100644
index 000000000..fafc0f07c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1b.right
@@ -0,0 +1,23 @@
+all    |0123456
+4:     |456
+4:2    |45
+4:-1   |45
+4:-2   |4
+4:-3   |
+-4:    |3456
+-4:2   |34
+-4:-1  |345
+-4:-2  |34
+-4:-3  |3
+-4:-4  |
+-4:i=2 |34
+-4:i=-2|34
+-4:i=-3|3
+-4:i=-4|
+-5:    |23456
+-6:    |123456
+-7:    |0123456
+-8:    |
+-9:    |
+-9:-99 |
+Ok:0
diff --git a/shell/hush_test/hush-vars/var_bash1b.tests b/shell/hush_test/hush-vars/var_bash1b.tests
new file mode 100755
index 000000000..efbdef35c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1b.tests
@@ -0,0 +1,24 @@
+set -- 0123456
+	echo "all    |"$1
+	echo "4:     |"${1:4}
+	echo "4:2    |"${1:4:2}
+	echo "4:-1   |"${1:4:-1}
+	echo "4:-2   |"${1:4:-2}
+	echo "4:-3   |"${1:4:-3}
+	echo "-4:    |"${1: -4}
+	echo "-4:2   |"${1: -4:2}
+	echo "-4:-1  |"${1: -4:-1}
+	echo "-4:-2  |"${1: -4:-2}
+	echo "-4:-3  |"${1: -4:-3}
+	echo "-4:-4  |"${1: -4:-4}
+i=2;	echo "-4:i=2 |"${1: -4:i}
+i=-2;	echo "-4:i=-2|"${1: -4:i}
+i=-3;	echo "-4:i=-3|"${1: -4:i}
+i=-4;	echo "-4:i=-4|"${1: -4:i}
+	echo "-5:    |"${1: -5}
+	echo "-6:    |"${1: -6}
+	echo "-7:    |"${1: -7}
+	echo "-8:    |"${1: -8}
+	echo "-9:    |"${1: -9}
+	echo "-9:-99 |"${1: -9:-99}
+echo Ok:$?
-- 
cgit v1.2.3-55-g6feb


From 0ba80e4fa251a1c753e5feaff4b358a427aa58cb Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 16:50:20 +0200
Subject: hush: small fix to last commit

die_if_script() indeed dies only in scripts! Must handle the case where it continues.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 836f3b83c..c8356f4b8 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5736,7 +5736,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				if (len < 0) /* bash compat */
 					die_if_script("%s: substring expression < 0", var);
 			}
-			if (len == 0 || !val || beg >= strlen(val)) {
+			if (len <= 0 || !val || beg >= strlen(val)) {
  arith_err:
 				val = NULL;
 			} else {
-- 
cgit v1.2.3-55-g6feb


From 4f8079de877cf2b7206e4cabaf465fb7d8cc4f62 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 17:11:48 +0200
Subject: ash: "you disabled math" is wrong: user did not disable it, builder
 of ash did

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 987d618c6..eb5156bea 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -11926,7 +11926,7 @@ parsesub: {
 #if ENABLE_FEATURE_SH_MATH
 			PARSEARITH();
 #else
-			raise_error_syntax("you disabled math support for $((arith)) syntax");
+			raise_error_syntax("support for $((arith)) is disabled");
 #endif
 		} else {
 			pungetc();
-- 
cgit v1.2.3-55-g6feb


From 826360ff238eb23191950dd4c5051195eab9733a Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 17:49:11 +0200
Subject: ash: more general format ${var:EXPR:EXPR}

function                                             old     new   delta
subevalvar                                          1171    1202     +31
localcmd                                             364     366      +2

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c                              | 43 +++++++++++++++++++-------------
 shell/ash_test/ash-vars/var_bash1b.right | 23 +++++++++++++++++
 shell/ash_test/ash-vars/var_bash1b.tests | 24 ++++++++++++++++++
 3 files changed, 73 insertions(+), 17 deletions(-)
 create mode 100644 shell/ash_test/ash-vars/var_bash1b.right
 create mode 100755 shell/ash_test/ash-vars/var_bash1b.tests

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index eb5156bea..aaf0561b8 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6612,7 +6612,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 	char *loc;
 	char *rmesc, *rmescend;
 	char *str;
-	IF_BASH_SUBSTR(int pos, len, orig_len;)
 	int amount, resetloc;
 	IF_BASH_PATTERN_SUBST(int workloc;)
 	IF_BASH_PATTERN_SUBST(char *repl = NULL;)
@@ -6641,14 +6640,23 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		/* NOTREACHED */
 
 #if BASH_SUBSTR
-	case VSSUBSTR:
-//TODO: support more general format ${v:EXPR:EXPR},
-// where EXPR follows $(()) rules
+	case VSSUBSTR: {
+		int pos, len, orig_len;
+		char *colon;
+
 		loc = str = stackblock() + strloc;
+
+# if !ENABLE_FEATURE_SH_MATH
+#  define ash_arith number
+# endif
 		/* Read POS in ${var:POS:LEN} */
-		pos = atoi(loc); /* number(loc) errors out on "1:4" */
-		len = str - startp - 1;
+		colon = strchr(loc, ':');
+		if (colon) *colon = '\0';
+		pos = ash_arith(loc);
+		if (colon) *colon = ':';
 
+		/* Read LEN in ${var:POS:LEN} */
+		len = str - startp - 1;
 		/* *loc != '\0', guaranteed by parser */
 		if (quotes) {
 			char *ptr;
@@ -6662,26 +6670,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 			}
 		}
 		orig_len = len;
-
 		if (*loc++ == ':') {
 			/* ${var::LEN} */
-			len = number(loc);
+			len = ash_arith(loc);
 		} else {
 			/* Skip POS in ${var:POS:LEN} */
 			len = orig_len;
 			while (*loc && *loc != ':') {
-				/* TODO?
-				 * bash complains on: var=qwe; echo ${var:1a:123}
-				if (!isdigit(*loc))
-					ash_msg_and_raise_error(msg_illnum, str);
-				 */
 				loc++;
 			}
 			if (*loc++ == ':') {
-				len = number(loc);
+				len = ash_arith(loc);
 			}
-//TODO: number() chokes on "-n". In bash, LEN=-n means strlen()-n
 		}
+#  undef ash_arith
+
 		if (pos < 0) {
 			/* ${VAR:$((-n)):l} starts n chars from the end */
 			pos = orig_len + pos;
@@ -6689,12 +6692,16 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		if ((unsigned)pos >= orig_len) {
 			/* apart from obvious ${VAR:999999:l},
 			 * covers ${VAR:$((-9999999)):l} - result is ""
-			 * (bash-compat)
+			 * (bash compat)
 			 */
 			pos = 0;
 			len = 0;
 		}
-		if (len > (orig_len - pos))
+		if (len < 0) {
+			/* ${VAR:N:-M} sets LEN to strlen()-M */
+			len = (orig_len - pos) + len;
+		}
+		if ((unsigned)len > (orig_len - pos))
 			len = orig_len - pos;
 
 		for (str = startp; pos; str++, pos--) {
@@ -6710,6 +6717,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		amount = loc - expdest;
 		STADJUST(amount, expdest);
 		return loc;
+	}
 #endif /* BASH_SUBSTR */
 	}
 
@@ -6754,6 +6762,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 #if BASH_PATTERN_SUBST
 	workloc = expdest - (char *)stackblock();
 	if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
+		int len;
 		char *idx, *end;
 
 		if (!repl) {
diff --git a/shell/ash_test/ash-vars/var_bash1b.right b/shell/ash_test/ash-vars/var_bash1b.right
new file mode 100644
index 000000000..fafc0f07c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash1b.right
@@ -0,0 +1,23 @@
+all    |0123456
+4:     |456
+4:2    |45
+4:-1   |45
+4:-2   |4
+4:-3   |
+-4:    |3456
+-4:2   |34
+-4:-1  |345
+-4:-2  |34
+-4:-3  |3
+-4:-4  |
+-4:i=2 |34
+-4:i=-2|34
+-4:i=-3|3
+-4:i=-4|
+-5:    |23456
+-6:    |123456
+-7:    |0123456
+-8:    |
+-9:    |
+-9:-99 |
+Ok:0
diff --git a/shell/ash_test/ash-vars/var_bash1b.tests b/shell/ash_test/ash-vars/var_bash1b.tests
new file mode 100755
index 000000000..efbdef35c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash1b.tests
@@ -0,0 +1,24 @@
+set -- 0123456
+	echo "all    |"$1
+	echo "4:     |"${1:4}
+	echo "4:2    |"${1:4:2}
+	echo "4:-1   |"${1:4:-1}
+	echo "4:-2   |"${1:4:-2}
+	echo "4:-3   |"${1:4:-3}
+	echo "-4:    |"${1: -4}
+	echo "-4:2   |"${1: -4:2}
+	echo "-4:-1  |"${1: -4:-1}
+	echo "-4:-2  |"${1: -4:-2}
+	echo "-4:-3  |"${1: -4:-3}
+	echo "-4:-4  |"${1: -4:-4}
+i=2;	echo "-4:i=2 |"${1: -4:i}
+i=-2;	echo "-4:i=-2|"${1: -4:i}
+i=-3;	echo "-4:i=-3|"${1: -4:i}
+i=-4;	echo "-4:i=-4|"${1: -4:i}
+	echo "-5:    |"${1: -5}
+	echo "-6:    |"${1: -6}
+	echo "-7:    |"${1: -7}
+	echo "-8:    |"${1: -8}
+	echo "-9:    |"${1: -9}
+	echo "-9:-99 |"${1: -9:-99}
+echo Ok:$?
-- 
cgit v1.2.3-55-g6feb


From 1e660422b16510f8bcdb764d632bb1da391c4a35 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 21:10:50 +0200
Subject: hush: implement "readonly" builtin

function                                             old     new   delta
builtin_readonly                                       -      70     +70
helper_export_local                                  152     174     +22
bltins1                                              348     360     +12
expand_one_var                                      1620    1625      +5
builtin_export                                       168     173      +5
set_pwd_var                                           36      40      +4
set_local_var                                        410     414      +4
set_vars_and_save_old                                 85      88      +3
set_local_var_from_halves                             24      27      +3
run_pipe                                            1551    1554      +3
run_list                                            1046    1048      +2
builtin_type                                         116     114      -2
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 10/1 up/down: 133/-2)           Total: 131 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 99 ++++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 63 insertions(+), 36 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index c8356f4b8..a68986329 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -48,7 +48,7 @@
  *      tilde expansion
  *      aliases
  *      builtins mandated by standards we don't support:
- *          [un]alias, command, fc, getopts, readonly, times:
+ *          [un]alias, command, fc, getopts, times:
  *          command -v CMD: print "/path/to/CMD"
  *              prints "CMD" for builtins
  *              prints "alias ALIAS='EXPANSION'" for aliases
@@ -57,8 +57,7 @@
  *          command [-p] CMD: run CMD, even if a function CMD also exists
  *              (can use this to override standalone shell as well)
  *              -p: use default $PATH
- *          readonly VAR[=VAL]...: make VARs readonly
- *          readonly [-p]: list all such VARs (-p has no effect in bash)
+ *          command BLTIN: disables special-ness (e.g. errors do not abort)
  *          getopts: getopt() for shells
  *          times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime
  *          fc -l[nr] [BEG] [END]: list range of commands in history
@@ -239,6 +238,12 @@
 //config:	help
 //config:	  export -n unexports variables. It is a bash extension.
 //config:
+//config:config HUSH_READONLY
+//config:	bool "readonly builtin"
+//config:	default y
+//config:	help
+//config:	  Enable support for read-only variables.
+//config:
 //config:config HUSH_KILL
 //config:	bool "kill builtin (supports kill %jobspec)"
 //config:	default y
@@ -964,6 +969,9 @@ static int builtin_exit(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_EXPORT
 static int builtin_export(char **argv) FAST_FUNC;
 #endif
+#if ENABLE_HUSH_READONLY
+static int builtin_readonly(char **argv) FAST_FUNC;
+#endif
 #if ENABLE_HUSH_JOB
 static int builtin_fg_bg(char **argv) FAST_FUNC;
 static int builtin_jobs(char **argv) FAST_FUNC;
@@ -1082,6 +1090,9 @@ static const struct built_in_command bltins1[] = {
 #if ENABLE_HUSH_READ
 	BLTIN("read"     , builtin_read    , "Input into variable"),
 #endif
+#if ENABLE_HUSH_READONLY
+	BLTIN("readonly" , builtin_readonly, "Make variables read-only"),
+#endif
 #if ENABLE_HUSH_FUNCTIONS
 	BLTIN("return"   , builtin_return  , "Return from function"),
 #endif
@@ -2052,19 +2063,10 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
  * -1: clear export flag and unsetenv the variable
  * flg_read_only is set only when we handle -R var=val
  */
-#if !BB_MMU && ENABLE_HUSH_LOCAL
-/* all params are used */
-#elif BB_MMU && ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-	set_local_var(str, flg_export, local_lvl)
-#elif BB_MMU && !ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-	set_local_var(str, flg_export)
-#elif !BB_MMU && !ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-	set_local_var(str, flg_export, flg_read_only)
-#endif
-static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
+static int set_local_var(char *str,
+		int flg_export UNUSED_PARAM,
+		int local_lvl UNUSED_PARAM,
+		int flg_read_only UNUSED_PARAM)
 {
 	struct variable **var_pp;
 	struct variable *cur;
@@ -2088,9 +2090,7 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
 
 		/* We found an existing var with this name */
 		if (cur->flg_read_only) {
-#if !BB_MMU
 			if (!flg_read_only)
-#endif
 				bb_error_msg("%s: readonly variable", str);
 			free(str);
 			return -1;
@@ -2158,10 +2158,12 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
 
  set_str_and_exp:
 	cur->varstr = str;
-#if !BB_MMU
-	cur->flg_read_only = flg_read_only;
-#endif
  exp:
+#if !BB_MMU || ENABLE_HUSH_READONLY
+	if (flg_read_only != 0) {
+		cur->flg_read_only = flg_read_only;
+	}
+#endif
 	if (flg_export == 1)
 		cur->flg_export = 1;
 	if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
@@ -9308,12 +9310,11 @@ static void print_escaped(const char *s)
 }
 #endif
 
-#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL
-# if !ENABLE_HUSH_LOCAL
-#define helper_export_local(argv, exp, lvl) \
-	helper_export_local(argv, exp)
-# endif
-static void helper_export_local(char **argv, int exp, int lvl)
+#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY
+static int helper_export_local(char **argv,
+		int exp UNUSED_PARAM,
+		int ro UNUSED_PARAM,
+		int lvl UNUSED_PARAM)
 {
 	do {
 		char *name = *argv;
@@ -9346,7 +9347,7 @@ static void helper_export_local(char **argv, int exp, int lvl)
 				}
 			}
 # if ENABLE_HUSH_LOCAL
-			if (exp == 0 /* local? */
+			if (exp == 0 && ro == 0 /* local? */
 			 && var && var->func_nest_level == lvl
 			) {
 				/* "local x=abc; ...; local x" - ignore second local decl */
@@ -9357,16 +9358,23 @@ static void helper_export_local(char **argv, int exp, int lvl)
 			 * bash does not put it in environment,
 			 * but remembers that it is exported,
 			 * and does put it in env when it is set later.
-			 * We just set it to "" and export. */
+			 * We just set it to "" and export.
+			 */
 			/* Or, it's "local NAME" (without =VALUE).
-			 * bash sets the value to "". */
+			 * bash sets the value to "".
+			 */
+			/* Or, it's "readonly NAME" (without =VALUE).
+			 * bash remembers NAME and disallows its creation
+			 * in the future.
+			 */
 			name = xasprintf("%s=", name);
 		} else {
 			/* (Un)exporting/making local NAME=VALUE */
 			name = xstrdup(name);
 		}
-		set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
+		set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ ro);
 	} while (*++argv);
+	return EXIT_SUCCESS;
 }
 #endif
 
@@ -9412,9 +9420,7 @@ static int FAST_FUNC builtin_export(char **argv)
 		return EXIT_SUCCESS;
 	}
 
-	helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
-
-	return EXIT_SUCCESS;
+	return helper_export_local(argv, /*exp:*/ (opt_unexport ? -1 : 1), /*ro:*/ 0, /*lvl:*/ 0);
 }
 #endif
 
@@ -9425,11 +9431,32 @@ static int FAST_FUNC builtin_local(char **argv)
 		bb_error_msg("%s: not in a function", argv[0]);
 		return EXIT_FAILURE; /* bash compat */
 	}
-	helper_export_local(argv, 0, G.func_nest_level);
-	return EXIT_SUCCESS;
+	argv++;
+	return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 0, /*lvl:*/ G.func_nest_level);
 }
 #endif
 
+#if ENABLE_HUSH_READONLY
+static int FAST_FUNC builtin_readonly(char **argv)
+{
+	if (*++argv == NULL) {
+		/* bash: readonly [-p]: list all readonly VARs
+		 * (-p has no effect in bash)
+		 */
+		struct variable *e;
+		for (e = G.top_var; e; e = e->next) {
+			if (e->flg_read_only) {
+//TODO: quote value: readonly VAR='VAL'
+				printf("readonly %s\n", e->varstr);
+			}
+		}
+		return EXIT_SUCCESS;
+	}
+	return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 1, /*lvl:*/ 0);
+}
+#endif
+
+
 #if ENABLE_HUSH_UNSET
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
 static int FAST_FUNC builtin_unset(char **argv)
-- 
cgit v1.2.3-55-g6feb


From b95ee96e7528554fc3ee2c48fbd59d75c59148db Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 21:19:53 +0200
Subject: hush: smaller code in !READONLY configs

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index a68986329..2125e757d 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9347,11 +9347,14 @@ static int helper_export_local(char **argv,
 				}
 			}
 # if ENABLE_HUSH_LOCAL
-			if (exp == 0 && ro == 0 /* local? */
-			 && var && var->func_nest_level == lvl
+			/* Is this "local" bltin? */
+			if (exp == 0
+			IF_HUSH_READONLY(&& ro == 0) /* in !READONLY config, always true */
 			) {
-				/* "local x=abc; ...; local x" - ignore second local decl */
-				continue;
+				if (var && var->func_nest_level == lvl) {
+					/* "local x=abc; ...; local x" - ignore second local decl */
+					continue;
+				}
 			}
 # endif
 			/* Exporting non-existing variable.
-- 
cgit v1.2.3-55-g6feb


From 6b48e1f1212300464c17c8317f5faddf6b414a4c Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 21:31:17 +0200
Subject: hush: forgot to emit error on (failing) second "readonly VAR=VAL"

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 2125e757d..1961c9830 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2074,6 +2074,7 @@ static int set_local_var(char *str,
 	char *eq_sign;
 	int name_len;
 
+	//bb_error_msg("set_local_var('%s',%d,%d,%d)", str, flg_export, local_lvl, flg_read_only);
 	eq_sign = strchr(str, '=');
 	if (!eq_sign) { /* not expected to ever happen? */
 		free(str);
@@ -2090,8 +2091,7 @@ static int set_local_var(char *str,
 
 		/* We found an existing var with this name */
 		if (cur->flg_read_only) {
-			if (!flg_read_only)
-				bb_error_msg("%s: readonly variable", str);
+			bb_error_msg("%s: readonly variable", str);
 			free(str);
 			return -1;
 		}
@@ -9459,7 +9459,6 @@ static int FAST_FUNC builtin_readonly(char **argv)
 }
 #endif
 
-
 #if ENABLE_HUSH_UNSET
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
 static int FAST_FUNC builtin_unset(char **argv)
-- 
cgit v1.2.3-55-g6feb


From 6b0695bb66dd38af4d4671fb2381fa3e1dbfe90c Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 17 Jul 2017 21:47:27 +0200
Subject: hush: HUSH_READONLY depends on HUSH

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 1 +
 1 file changed, 1 insertion(+)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 1961c9830..4b71344da 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -241,6 +241,7 @@
 //config:config HUSH_READONLY
 //config:	bool "readonly builtin"
 //config:	default y
+//config:	depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:	help
 //config:	  Enable support for read-only variables.
 //config:
-- 
cgit v1.2.3-55-g6feb


From 3bab36b18baa0dc254445828f492051450a38d41 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 18 Jul 2017 01:05:24 +0200
Subject: hush: convert exp/ro/local parameters to bitfields in one flag param

function                                             old     new   delta
helper_export_local                                  174     185     +11
set_local_var                                        424     420      -4
run_list                                            1048    1044      -4
set_vars_and_save_old                                 88      83      -5
set_local_var_from_halves                             27      22      -5
run_pipe                                            1554    1549      -5
builtin_export                                       173     168      -5
set_pwd_var                                           40      34      -6
builtin_readonly                                      70      64      -6
expand_one_var                                      1625    1618      -7
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/9 up/down: 11/-47)            Total: -36 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 81 ++++++++++++++++++++++++++----------------------------------
 1 file changed, 35 insertions(+), 46 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 4b71344da..7771172b6 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2057,25 +2057,20 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
 
 /* str holds "NAME=VAL" and is expected to be malloced.
  * We take ownership of it.
- * flg_export:
- *  0: do not change export flag
- *     (if creating new variable, flag will be 0)
- *  1: set export flag and putenv the variable
- * -1: clear export flag and unsetenv the variable
- * flg_read_only is set only when we handle -R var=val
  */
-static int set_local_var(char *str,
-		int flg_export UNUSED_PARAM,
-		int local_lvl UNUSED_PARAM,
-		int flg_read_only UNUSED_PARAM)
+#define SETFLAG_EXPORT   (1 << 0)
+#define SETFLAG_UNEXPORT (1 << 1)
+#define SETFLAG_MAKE_RO  (1 << 2)
+#define SETFLAG_LOCAL_SHIFT    3
+static int set_local_var(char *str, unsigned flags)
 {
 	struct variable **var_pp;
 	struct variable *cur;
 	char *free_me = NULL;
 	char *eq_sign;
 	int name_len;
+	IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);)
 
-	//bb_error_msg("set_local_var('%s',%d,%d,%d)", str, flg_export, local_lvl, flg_read_only);
 	eq_sign = strchr(str, '=');
 	if (!eq_sign) { /* not expected to ever happen? */
 		free(str);
@@ -2096,7 +2091,7 @@ static int set_local_var(char *str,
 			free(str);
 			return -1;
 		}
-		if (flg_export == -1) { // "&& cur->flg_export" ?
+		if (flags & SETFLAG_UNEXPORT) { // && cur->flg_export ?
 			debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
 			*eq_sign = '\0';
 			unsetenv(str);
@@ -2120,7 +2115,7 @@ static int set_local_var(char *str,
 			 * z=z
 			 */
 			if (cur->flg_export)
-				flg_export = 1;
+				flags |= SETFLAG_EXPORT;
 			break;
 		}
 #endif
@@ -2151,9 +2146,7 @@ static int set_local_var(char *str,
 
 	/* Not found - create new variable struct */
 	cur = xzalloc(sizeof(*cur));
-#if ENABLE_HUSH_LOCAL
-	cur->func_nest_level = local_lvl;
-#endif
+	IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;)
 	cur->next = *var_pp;
 	*var_pp = cur;
 
@@ -2161,16 +2154,16 @@ static int set_local_var(char *str,
 	cur->varstr = str;
  exp:
 #if !BB_MMU || ENABLE_HUSH_READONLY
-	if (flg_read_only != 0) {
-		cur->flg_read_only = flg_read_only;
+	if (flags & SETFLAG_MAKE_RO) {
+		cur->flg_read_only = 1;
 	}
 #endif
-	if (flg_export == 1)
+	if (flags & SETFLAG_EXPORT)
 		cur->flg_export = 1;
 	if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
 		cmdedit_update_prompt();
 	if (cur->flg_export) {
-		if (flg_export == -1) {
+		if (flags & SETFLAG_UNEXPORT) {
 			cur->flg_export = 0;
 			/* unsetenv was already done */
 		} else {
@@ -2187,10 +2180,9 @@ static int set_local_var(char *str,
 }
 
 /* Used at startup and after each cd */
-static void set_pwd_var(int exp)
+static void set_pwd_var(unsigned flag)
 {
-	set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)),
-		/*exp:*/ exp, /*lvl:*/ 0, /*ro:*/ 0);
+	set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag);
 }
 
 static int unset_local_var_len(const char *name, int name_len)
@@ -2248,7 +2240,7 @@ static void unset_vars(char **strings)
 static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
 {
 	char *var = xasprintf("%s=%s", name, val);
-	set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+	set_local_var(var, /*flag:*/ 0);
 }
 #endif
 
@@ -2299,7 +2291,7 @@ static struct variable *set_vars_and_save_old(char **strings)
 				var_p->next = old;
 				old = var_p;
 			}
-			set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
+			set_local_var(*s, SETFLAG_EXPORT);
 		}
 		s++;
 	}
@@ -5805,7 +5797,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 						val = NULL;
 					} else {
 						char *new_var = xasprintf("%s=%s", var, val);
-						set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+						set_local_var(new_var, /*flag:*/ 0);
 					}
 				}
 			}
@@ -7775,7 +7767,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			if (new_env) {
 				argv = new_env;
 				while (*argv) {
-					set_local_var(*argv, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+					set_local_var(*argv, /*flag:*/ 0);
 					/* Do we need to flag set_local_var() errors?
 					 * "assignment to readonly var" and "putenv error"
 					 */
@@ -7803,7 +7795,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 					fprintf(stderr, " %s", p);
 				debug_printf_exec("set shell var:'%s'->'%s'\n",
 						*argv, p);
-				set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+				set_local_var(p, /*flag:*/ 0);
 				/* Do we need to flag set_local_var() errors?
 				 * "assignment to readonly var" and "putenv error"
 				 */
@@ -8215,7 +8207,7 @@ static int run_list(struct pipe *pi)
 			}
 			/* Insert next value from for_lcur */
 			/* note: *for_lcur already has quotes removed, $var expanded, etc */
-			set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+			set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*flag:*/ 0);
 			continue;
 		}
 		if (rword == RES_IN) {
@@ -8600,7 +8592,7 @@ int hush_main(int argc, char **argv)
 	putenv(shell_ver->varstr);
 
 	/* Export PWD */
-	set_pwd_var(/*exp:*/ 1);
+	set_pwd_var(SETFLAG_EXPORT);
 
 #if BASH_HOSTNAME_VAR
 	/* Set (but not export) HOSTNAME unless already set */
@@ -8770,7 +8762,7 @@ int hush_main(int argc, char **argv)
 		}
 		case 'R':
 		case 'V':
-			set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
+			set_local_var(xstrdup(optarg), opt == 'R' ? SETFLAG_MAKE_RO : 0);
 			break;
 # if ENABLE_HUSH_FUNCTIONS
 		case 'F': {
@@ -9073,7 +9065,7 @@ static int FAST_FUNC builtin_cd(char **argv)
 	 * Note: do not enforce exporting. If PWD was unset or unexported,
 	 * set it again, but do not export. bash does the same.
 	 */
-	set_pwd_var(/*exp:*/ 0);
+	set_pwd_var(/*flag:*/ 0);
 	return EXIT_SUCCESS;
 }
 
@@ -9312,10 +9304,7 @@ static void print_escaped(const char *s)
 #endif
 
 #if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY
-static int helper_export_local(char **argv,
-		int exp UNUSED_PARAM,
-		int ro UNUSED_PARAM,
-		int lvl UNUSED_PARAM)
+static int helper_export_local(char **argv, unsigned flags)
 {
 	do {
 		char *name = *argv;
@@ -9329,7 +9318,7 @@ static int helper_export_local(char **argv,
 			vpp = get_ptr_to_local_var(name, name_end - name);
 			var = vpp ? *vpp : NULL;
 
-			if (exp == -1) { /* unexporting? */
+			if (flags & SETFLAG_UNEXPORT) {
 				/* export -n NAME (without =VALUE) */
 				if (var) {
 					var->flg_export = 0;
@@ -9338,7 +9327,7 @@ static int helper_export_local(char **argv,
 				} /* else: export -n NOT_EXISTING_VAR: no-op */
 				continue;
 			}
-			if (exp == 1) { /* exporting? */
+			if (flags & SETFLAG_EXPORT) {
 				/* export NAME (without =VALUE) */
 				if (var) {
 					var->flg_export = 1;
@@ -9349,9 +9338,8 @@ static int helper_export_local(char **argv,
 			}
 # if ENABLE_HUSH_LOCAL
 			/* Is this "local" bltin? */
-			if (exp == 0
-			IF_HUSH_READONLY(&& ro == 0) /* in !READONLY config, always true */
-			) {
+			if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
+				unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT;
 				if (var && var->func_nest_level == lvl) {
 					/* "local x=abc; ...; local x" - ignore second local decl */
 					continue;
@@ -9376,7 +9364,7 @@ static int helper_export_local(char **argv,
 			/* (Un)exporting/making local NAME=VALUE */
 			name = xstrdup(name);
 		}
-		set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ ro);
+		set_local_var(name, flags);
 	} while (*++argv);
 	return EXIT_SUCCESS;
 }
@@ -9424,7 +9412,7 @@ static int FAST_FUNC builtin_export(char **argv)
 		return EXIT_SUCCESS;
 	}
 
-	return helper_export_local(argv, /*exp:*/ (opt_unexport ? -1 : 1), /*ro:*/ 0, /*lvl:*/ 0);
+	return helper_export_local(argv, opt_unexport ? SETFLAG_UNEXPORT : SETFLAG_EXPORT);
 }
 #endif
 
@@ -9436,14 +9424,15 @@ static int FAST_FUNC builtin_local(char **argv)
 		return EXIT_FAILURE; /* bash compat */
 	}
 	argv++;
-	return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 0, /*lvl:*/ G.func_nest_level);
+	return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT);
 }
 #endif
 
 #if ENABLE_HUSH_READONLY
 static int FAST_FUNC builtin_readonly(char **argv)
 {
-	if (*++argv == NULL) {
+	argv++;
+	if (*argv == NULL) {
 		/* bash: readonly [-p]: list all readonly VARs
 		 * (-p has no effect in bash)
 		 */
@@ -9456,7 +9445,7 @@ static int FAST_FUNC builtin_readonly(char **argv)
 		}
 		return EXIT_SUCCESS;
 	}
-	return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 1, /*lvl:*/ 0);
+	return helper_export_local(argv, SETFLAG_MAKE_RO);
 }
 #endif
 
-- 
cgit v1.2.3-55-g6feb


From 38ef39a1abd46ca390b0259ebd0b35e9ea9ccb68 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 18 Jul 2017 01:40:01 +0200
Subject: hush: add readonly testcase, fix fallout

function                                             old     new   delta
helper_export_local                                  185     214     +29
run_pipe                                            1549    1560     +11
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 40/0)               Total: 40 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                              | 26 +++++++++++++++++---------
 shell/hush_test/hush-vars/readonly0.right | 12 ++++++++++++
 shell/hush_test/hush-vars/readonly0.tests | 24 ++++++++++++++++++++++++
 3 files changed, 53 insertions(+), 9 deletions(-)
 create mode 100644 shell/hush_test/hush-vars/readonly0.right
 create mode 100755 shell/hush_test/hush-vars/readonly0.tests

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 7771172b6..eab1284f6 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7767,10 +7767,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			if (new_env) {
 				argv = new_env;
 				while (*argv) {
-					set_local_var(*argv, /*flag:*/ 0);
-					/* Do we need to flag set_local_var() errors?
-					 * "assignment to readonly var" and "putenv error"
-					 */
+					if (set_local_var(*argv, /*flag:*/ 0)) {
+						/* assignment to readonly var / putenv error? */
+						rcode = 1;
+					}
 					argv++;
 				}
 			}
@@ -7795,10 +7795,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
 					fprintf(stderr, " %s", p);
 				debug_printf_exec("set shell var:'%s'->'%s'\n",
 						*argv, p);
-				set_local_var(p, /*flag:*/ 0);
-				/* Do we need to flag set_local_var() errors?
-				 * "assignment to readonly var" and "putenv error"
-				 */
+				if (set_local_var(p, /*flag:*/ 0)) {
+					/* assignment to readonly var / putenv error? */
+					rcode = 1;
+				}
 				argv++;
 			}
 			if (G_x_mode)
@@ -9336,6 +9336,13 @@ static int helper_export_local(char **argv, unsigned flags)
 					continue;
 				}
 			}
+			if (flags & SETFLAG_MAKE_RO) {
+				/* readonly NAME (without =VALUE) */
+				if (var) {
+					var->flg_read_only = 1;
+					continue;
+				}
+			}
 # if ENABLE_HUSH_LOCAL
 			/* Is this "local" bltin? */
 			if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
@@ -9364,7 +9371,8 @@ static int helper_export_local(char **argv, unsigned flags)
 			/* (Un)exporting/making local NAME=VALUE */
 			name = xstrdup(name);
 		}
-		set_local_var(name, flags);
+		if (set_local_var(name, flags))
+			return EXIT_FAILURE;
 	} while (*++argv);
 	return EXIT_SUCCESS;
 }
diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
new file mode 100644
index 000000000..9688d2e5f
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -0,0 +1,12 @@
+readonly a=A
+readonly b=B
+Ok:0
+hush: a=A: readonly variable
+Fail:1
+hush: a=A: readonly variable
+Fail:1
+hush: a=A: readonly variable
+Fail:1
+Visible:0
+hush: a: readonly variable
+Fail:1
diff --git a/shell/hush_test/hush-vars/readonly0.tests b/shell/hush_test/hush-vars/readonly0.tests
new file mode 100755
index 000000000..3845f76ac
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.tests
@@ -0,0 +1,24 @@
+readonly a=A
+b=B
+readonly b
+# readonly on already readonly var is harmless
+readonly b a
+readonly | grep '^readonly [ab]='
+# this should work
+export a b
+export -n a b
+echo Ok:$?
+env | grep -e^a= -e^b=  # shows nothing
+
+# these should all fail (despite the same value being assigned)
+# bash does not abort even in non-interactive more (in script)
+true
+a=A
+echo Fail:$?; true
+readonly a=A
+echo Fail:$?; true
+export a=A
+echo Fail:$?; true
+a=A echo Visible:$? # command still runs
+unset a
+echo Fail:$?; true
-- 
cgit v1.2.3-55-g6feb


From 5b2cc0aaee6985431d9bab1b49ceea7e1fa1d7af Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 18 Jul 2017 02:44:06 +0200
Subject: hush: do not assign to readonly VAR in "VAR=VAL CMD" syntax too

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                              |  9 ++++++++-
 shell/hush_test/hush-vars/readonly0.right |  6 ++++--
 shell/hush_test/hush-vars/readonly0.tests | 30 +++++++++++++++++++++---------
 3 files changed, 33 insertions(+), 12 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index eab1284f6..55e581e16 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2089,6 +2089,8 @@ static int set_local_var(char *str, unsigned flags)
 		if (cur->flg_read_only) {
 			bb_error_msg("%s: readonly variable", str);
 			free(str);
+//NOTE: in bash, assignment in "export READONLY_VAR=Z" fails, and sets $?=1,
+//but export per se succeeds (does put the var in env). We don't mimic that.
 			return -1;
 		}
 		if (flags & SETFLAG_UNEXPORT) { // && cur->flg_export ?
@@ -2283,8 +2285,12 @@ static struct variable *set_vars_and_save_old(char **strings)
 		if (eq) {
 			var_pp = get_ptr_to_local_var(*s, eq - *s);
 			if (var_pp) {
-				/* Remove variable from global linked list */
 				var_p = *var_pp;
+				if (var_p->flg_read_only) {
+					bb_error_msg("%s: readonly variable", *s);
+					goto next;
+				}
+				/* Remove variable from global linked list */
 				debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr);
 				*var_pp = var_p->next;
 				/* Add it to returned list */
@@ -2293,6 +2299,7 @@ static struct variable *set_vars_and_save_old(char **strings)
 			}
 			set_local_var(*s, SETFLAG_EXPORT);
 		}
+ next:
 		s++;
 	}
 	return old;
diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
index 9688d2e5f..07ca6e07f 100644
--- a/shell/hush_test/hush-vars/readonly0.right
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -5,8 +5,10 @@ hush: a=A: readonly variable
 Fail:1
 hush: a=A: readonly variable
 Fail:1
-hush: a=A: readonly variable
+hush: a=Z: readonly variable
 Fail:1
-Visible:0
+hush: a=Z: readonly variable
+b=B
+^^^a is not exported
 hush: a: readonly variable
 Fail:1
diff --git a/shell/hush_test/hush-vars/readonly0.tests b/shell/hush_test/hush-vars/readonly0.tests
index 3845f76ac..3ace9b767 100755
--- a/shell/hush_test/hush-vars/readonly0.tests
+++ b/shell/hush_test/hush-vars/readonly0.tests
@@ -1,10 +1,12 @@
+unset a b
+
 readonly a=A
 b=B
 readonly b
-# readonly on already readonly var is harmless
+# readonly on already readonly var is harmless:
 readonly b a
 readonly | grep '^readonly [ab]='
-# this should work
+# this should work:
 export a b
 export -n a b
 echo Ok:$?
@@ -12,13 +14,23 @@ env | grep -e^a= -e^b=  # shows nothing
 
 # these should all fail (despite the same value being assigned)
 # bash does not abort even in non-interactive more (in script)
-true
-a=A
-echo Fail:$?; true
-readonly a=A
-echo Fail:$?; true
-export a=A
+true; a=A
+echo Fail:$?
+true; readonly a=A
+echo Fail:$?
+
+# in bash, assignment in export fails, but export succeeds! :)
+# we don't mimic that!
+true; export a=Z
 echo Fail:$?; true
-a=A echo Visible:$? # command still runs
+#env | grep '^a='
+#echo "^^^a is exported"
+export -n a  # undo that bashism, if it happens
+
+export b
+# this fails to both set and export a:
+a=Z env | grep '^[ab]='  # command still runs
+echo "^^^a is not exported"
+
 unset a
 echo Fail:$?; true
-- 
cgit v1.2.3-55-g6feb


From f645e1573c5521c87b972400f9b4abc3636983d4 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 18 Jul 2017 03:23:07 +0200
Subject: hush: another testcase for "READONLY_VAR=VAL BLTIN ..."

Currently fails.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush_test/hush-vars/readonly0.right |  6 ++++++
 shell/hush_test/hush-vars/readonly0.tests | 16 +++++++++++-----
 shell/hush_test/hush-vars/readonly2.right |  4 ++++
 shell/hush_test/hush-vars/readonly2.tests |  6 ++++++
 4 files changed, 27 insertions(+), 5 deletions(-)
 create mode 100644 shell/hush_test/hush-vars/readonly2.right
 create mode 100755 shell/hush_test/hush-vars/readonly2.tests

(limited to 'shell')

diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
index 07ca6e07f..8b750eb5f 100644
--- a/shell/hush_test/hush-vars/readonly0.right
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -1,14 +1,20 @@
 readonly a=A
 readonly b=B
 Ok:0
+
 hush: a=A: readonly variable
 Fail:1
 hush: a=A: readonly variable
 Fail:1
+
 hush: a=Z: readonly variable
 Fail:1
+
 hush: a=Z: readonly variable
 b=B
 ^^^a is not exported
+hush: a=Z: readonly variable
+Visible:42
+
 hush: a: readonly variable
 Fail:1
diff --git a/shell/hush_test/hush-vars/readonly0.tests b/shell/hush_test/hush-vars/readonly0.tests
index 3ace9b767..0833ccf29 100755
--- a/shell/hush_test/hush-vars/readonly0.tests
+++ b/shell/hush_test/hush-vars/readonly0.tests
@@ -1,5 +1,5 @@
 unset a b
-
+#
 readonly a=A
 b=B
 readonly b
@@ -12,6 +12,7 @@ export -n a b
 echo Ok:$?
 env | grep -e^a= -e^b=  # shows nothing
 
+echo
 # these should all fail (despite the same value being assigned)
 # bash does not abort even in non-interactive more (in script)
 true; a=A
@@ -19,18 +20,23 @@ echo Fail:$?
 true; readonly a=A
 echo Fail:$?
 
+echo
 # in bash, assignment in export fails, but export succeeds! :)
 # we don't mimic that!
 true; export a=Z
-echo Fail:$?; true
+echo Fail:$?
 #env | grep '^a='
 #echo "^^^a is exported"
 export -n a  # undo that bashism, if it happens
 
+echo
 export b
 # this fails to both set and export a:
-a=Z env | grep '^[ab]='  # command still runs
+a=Z env | grep '^[ab]='
 echo "^^^a is not exported"
+# but external command does get executed, and $? is not mangled (stays 42):
+(exit 42); a=Z env echo Visible:$?
 
-unset a
-echo Fail:$?; true
+echo
+true; unset a
+echo Fail:$?
diff --git a/shell/hush_test/hush-vars/readonly2.right b/shell/hush_test/hush-vars/readonly2.right
new file mode 100644
index 000000000..5b02ddfe8
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly2.right
@@ -0,0 +1,4 @@
+hush: a=Z: readonly variable
+Visible:42
+hush: a=Z: readonly variable
+Visible:42
diff --git a/shell/hush_test/hush-vars/readonly2.tests b/shell/hush_test/hush-vars/readonly2.tests
new file mode 100755
index 000000000..d9d178edd
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly2.tests
@@ -0,0 +1,6 @@
+unset a
+readonly a=A
+
+# external commands and builtins should behave the same:
+(exit 42); a=Z echo "Visible:$?"
+(exit 42); a=Z env echo "Visible:$?"
-- 
cgit v1.2.3-55-g6feb


From cf5110978ba25002ec5cb46aaae2472eb66001ac Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 18 Jul 2017 15:58:02 +0200
Subject: hush: fix readonly2.tests failure

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c                              | 11 +++++++++++
 shell/hush_test/hush-vars/readonly2.right |  1 +
 shell/hush_test/hush-vars/readonly2.tests |  1 +
 3 files changed, 13 insertions(+)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 55e581e16..f6da826d3 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2287,7 +2287,18 @@ static struct variable *set_vars_and_save_old(char **strings)
 			if (var_pp) {
 				var_p = *var_pp;
 				if (var_p->flg_read_only) {
+					char **p;
 					bb_error_msg("%s: readonly variable", *s);
+					/*
+					 * "VAR=V BLTIN" unsets VARs after BLTIN completes.
+					 * If VAR is readonly, leaving it in the list
+					 * after asssignment error (msg above)
+					 * causes doubled error message later, on unset.
+					 */
+					debug_printf_env("removing/freeing '%s' element\n", *s);
+					free(*s);
+					p = s;
+					do { *p = p[1]; p++; } while (*p);
 					goto next;
 				}
 				/* Remove variable from global linked list */
diff --git a/shell/hush_test/hush-vars/readonly2.right b/shell/hush_test/hush-vars/readonly2.right
index 5b02ddfe8..38551d4ad 100644
--- a/shell/hush_test/hush-vars/readonly2.right
+++ b/shell/hush_test/hush-vars/readonly2.right
@@ -1,4 +1,5 @@
 hush: a=Z: readonly variable
 Visible:42
+
 hush: a=Z: readonly variable
 Visible:42
diff --git a/shell/hush_test/hush-vars/readonly2.tests b/shell/hush_test/hush-vars/readonly2.tests
index d9d178edd..b758d9602 100755
--- a/shell/hush_test/hush-vars/readonly2.tests
+++ b/shell/hush_test/hush-vars/readonly2.tests
@@ -3,4 +3,5 @@ readonly a=A
 
 # external commands and builtins should behave the same:
 (exit 42); a=Z echo "Visible:$?"
+echo
 (exit 42); a=Z env echo "Visible:$?"
-- 
cgit v1.2.3-55-g6feb