u-boot.lists.denx.de archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v7 00/23] Modernize U-Boot shell
@ 2023-03-30 19:46 Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 01/23] test: Add framework to test hush behavior Francis Laniel
                   ` (22 more replies)
  0 siblings, 23 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Alexey Romanov, Patrice Chotard,
	Fabrice Gasnier, Hector Palacios, Marek Vasut, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Linus Walleij, Andrew Scull

Hi.


During 2021 summer, Sean Anderson wrote a contribution to add a new shell, based
on LIL, to U-Boot [1, 2].
While one of the goals of this contribution was to address the fact actual
U-Boot shell, which is based on Busybox hush, is old there was a discussion
about adding a new shell versus updating the actual one [3, 4].

So, in this series, with Harald Seiler, we updated the actual U-Boot shell to
reflect what is currently in Busybox source code.
Basically, this contribution is about taking a snapshot of Busybox shell/hush.c
file (as it exists in commit 37460f5da) and adapt it to suit U-Boot needs.

This contribution was written to be as backward-compatible as possible to avoid
breaking the existing.
So, the 2021 hush flavor offers the same as the actual, that is to say:
1. Variable expansion.
2. Instruction lists (;, && and ||).
3. If, then and else.
4. Loops (for, while and until).
No new features offered by Busybox hush were implemented (e.g. functions).

It is possible to change the parser at runtime using the "parser" command:
=> parser print
old
=> parser set 2021
=> parser print
2021
=> parser set old
The default parser is the old one.
Note that to use both parser, you would need to set both CONFIG_HUSH_2021_PARSER
and CONFIG_HUSH_OLD_PARSER.

In terms of testing, new unit tests were added to ut to ensure the new behavior
is the same as the old one and it does not add regression.
Nonetheless, if old behavior was buggy and fixed upstream, the fix is then added
to U-Boot [5].
In sandbox, all of these tests pass smoothly:
=> printenv board
board=sandbox
=> ut hush
Running 20 hush tests
...
Failures: 0
=> parser set 2021
=> ut hush
Running 20 hush tests
...
Failures: 0

Thanks to the effort of Harald Seiler, I was successful booting a board:
=> printenv fdtfile
fdtfile=amlogic/meson-gxl-s905x-libretech-cc.dtb
=> parser get
old
=> boot
...
root@lepotato:~#
root@lepotato:~# reboot
...
=> parser set 2021
=> parser get
2021
=> printenv fdtfile
fdtfile=amlogic/meson-gxl-s905x-libretech-cc.dtb
=> boot
...
root@lepotato:~#

I was able to have the CI passes but I had to not use CONFIG_HUSH_2021_PARSER
for the keymile board.
Indeed, the keymile board family is the only set of boards to call
get_local_var(), set_local_var() and unset_local_var().
Sadly, these functions are static in this contribution.
I could have change all of them to introduce code like this:
*_local_var(/*...*/)
{
        if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
                return *_local_var_old(/*...*/);
        if (gd->flags & GD_FLG_HUSH_2021_PARSER)
                return *_local_var_2021(/*...*/);
}
But this would have mean renaming all old hush functions calls and I did not
want to change the old hush particularly to avoid breaking things.
Instead, I change the keymile board to use environment variable instead of local
ones.
I think this particularities can be addressed in future works.

I also had to enable CONFIG_LTO for kirkwoord sheevaplug and phytec bk4r1, so
they do not hit their limits.

For all these reasons, I marked this contribution as RFC to indeed collect your
opinions.
My goal is not to change suddenly actual shell to this one, we clearly need a
transition period to think about it.
I think it is better to see this contribution as a proof of concept which shows
it is possible to update the actual shell.

If you want to review it - your review will really be appreciated - here are
some information regarding the commits:
* commits marked as "test:" deal with unit tests.
* commit "cli: Add Busybox upstream hush.c file." copies Busybox shell/hush.c
into U-Boot tree, this explain why this commit contains around 12000 additions.
* commit "cli: Port Busybox 2021 hush to U-Boot." modifies previously added file
to permit us to use this as new shell.
The really good idea of #include'ing Busybox code into a wrapper file to define
some particular functions while minimizing modifications to upstream code comes
from Harald Seiler.
* commit "cmd: Add new parser command" adds a new command which permits
selecting parser at runtime.
I am not really satisfied with the fact it calls cli_init() and cli_loop() each
time the parser is set, so your reviews would be welcomed.
* Other commits focus on enabling features we need (e.g. if).

Changes since:
 v2:
  * Added a small fix to compile sandbox with NO_SDL=1.
  * Added a command to change parser at runtime.
  * Added 2021 parser function to all run_command*().
 v3:
  * Various bug fixes pointed by the CI.
  * Added upstream busybox hush commits until 6th February 2022.
 v4:
  * Various cleaning.
  * Modified python test to accept failure output when the test are designed to
  fail.
  * Bumped upstream busybox hush commits until 24h March 2022.
 v5:
  * Bumped upstream busybox hush commits until 30th January 2023.
  * Fix how hush interprets '<' and '>', indeed we needed to escape them but I
  removed this behavior as tests are handled by test command and not hush
  itself. This permitted to have the ut fdt to pass.
  * Fix a problem with how exit was handled. This was reported by the ut exit
  test.

Francis Laniel (23):
  test: Add framework to test hush behavior
  test: hush: Test hush if/else
  test/py: hush_if_test: Remove the test file
  test: hush: Test hush variable expansion
  test: hush: Test hush commands list
  test: hush: Test hush loops
  cli: Add Busybox upstream hush.c file
  cli: Port Busybox 2021 hush to U-Boot
  cli: Add menu for hush parser
  global_data.h: add GD_FLG_HUSH_OLD_PARSER flag
  cmd: Add new parser command
  cli: Enables using hush 2021 parser as command line parser
  cli: hush_2021: Enable variables expansion for hush 2021
  cli: hush_2021: Add functions to be called from run_command()
  cli: add hush 2021 as parser for run_command*()
  test: hush: Fix instructions list tests for hush 2021
  test: hush: Fix variable expansion tests for hush 2021
  cli: hush_2021: Enable using < and > as string compare operators
  cli: hush_2021: Enable if keyword
  cli: hush_2021: Enable loops
  test: hush: Fix loop tests for hush 2021
  cli: hush_2021: Add upstream commits up to 30th January 2023.
  DO NOT MERGE: only to make CI happy

 arch/arm/lib/lib1funcs.S           |    17 +
 cmd/Kconfig                        |    22 +
 cmd/Makefile                       |     2 +
 cmd/parser.c                       |   125 +
 common/Makefile                    |     3 +-
 common/cli.c                       |    82 +-
 common/cli_hush_2021.c             |   306 +
 common/cli_hush_upstream.c         | 12969 +++++++++++++++++++++++++++
 configs/bk4r1_defconfig            |     1 +
 configs/sheevaplug_defconfig       |     1 +
 include/asm-generic/global_data.h  |     8 +
 include/cli_hush.h                 |    51 +-
 include/test/hush.h                |    15 +
 include/test/suites.h              |     1 +
 test/Makefile                      |     3 +
 test/cmd_ut.c                      |     6 +
 test/hush/Makefile                 |    10 +
 test/hush/cmd_ut_hush.c            |    20 +
 test/hush/dollar.c                 |   226 +
 test/hush/if.c                     |   308 +
 test/hush/list.c                   |   140 +
 test/hush/loop.c                   |    91 +
 test/py/tests/test_hush_if_test.py |   184 -
 test/py/tests/test_ut.py           |     8 +-
 24 files changed, 14400 insertions(+), 199 deletions(-)
 create mode 100644 cmd/parser.c
 create mode 100644 common/cli_hush_2021.c
 create mode 100644 common/cli_hush_upstream.c
 create mode 100644 include/test/hush.h
 create mode 100644 test/hush/Makefile
 create mode 100644 test/hush/cmd_ut_hush.c
 create mode 100644 test/hush/dollar.c
 create mode 100644 test/hush/if.c
 create mode 100644 test/hush/list.c
 create mode 100644 test/hush/loop.c
 delete mode 100644 test/py/tests/test_hush_if_test.py


Best regards and thank you in advance.
--
2.34.1


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 01/23] test: Add framework to test hush behavior
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 02/23] test: hush: Test hush if/else Francis Laniel
                   ` (21 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Samuel Dionne-Riel, Alexey Romanov,
	Patrice Chotard, Marek Vasut, Fabrice Gasnier, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Rui Miguel Silva,
	Linus Walleij, Andrew Scull

Introduce a new subcommand to ut: ut hush.
For the moment, this command does nothing, future commits will add tests which
will be run on command call.

Note that CONFIG_HUSH_PARSER must be defined to compile this new subcommand.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 include/test/hush.h     | 15 +++++++++++++++
 include/test/suites.h   |  1 +
 test/Makefile           |  3 +++
 test/cmd_ut.c           |  6 ++++++
 test/hush/Makefile      |  6 ++++++
 test/hush/cmd_ut_hush.c | 20 ++++++++++++++++++++
 6 files changed, 51 insertions(+)
 create mode 100644 include/test/hush.h
 create mode 100644 test/hush/Makefile
 create mode 100644 test/hush/cmd_ut_hush.c

diff --git a/include/test/hush.h b/include/test/hush.h
new file mode 100644
index 0000000000..cca66544a0
--- /dev/null
+++ b/include/test/hush.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#ifndef __TEST_HUSH_H__
+#define __TEST_HUSH_H__
+
+#include <test/test.h>
+
+/* Declare a new environment test */
+#define HUSH_TEST(_name, _flags)	UNIT_TEST(_name, _flags, hush_test)
+
+#endif /* __TEST_HUSH_H__ */
diff --git a/include/test/suites.h b/include/test/suites.h
index 7c4960c004..9a25bb0959 100644
--- a/include/test/suites.h
+++ b/include/test/suites.h
@@ -41,6 +41,7 @@ int do_ut_env(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_exit(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_fdt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_font(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+int do_ut_hush(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_lib(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_loadm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_log(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]);
diff --git a/test/Makefile b/test/Makefile
index 178773647a..d6ca3e221d 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -20,6 +20,9 @@ obj-$(CONFIG_FUZZ) += fuzz/
 ifndef CONFIG_SANDBOX_VPL
 obj-$(CONFIG_UNIT_TEST) += lib/
 endif
+ifneq ($(CONFIG_HUSH_PARSER),)
+obj-$(CONFIG_$(SPL_)CMDLINE) += hush/
+endif
 obj-$(CONFIG_$(SPL_)CMDLINE) += print_ut.o
 obj-$(CONFIG_$(SPL_)CMDLINE) += str_ut.o
 obj-$(CONFIG_UT_TIME) += time_ut.o
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 409c22bfd2..98f2a7e38a 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -107,6 +107,9 @@ static struct cmd_tbl cmd_ut_sub[] = {
 #ifdef CONFIG_CMD_ADDRMAP
 	U_BOOT_CMD_MKENT(addrmap, CONFIG_SYS_MAXARGS, 1, do_ut_addrmap, "", ""),
 #endif
+#if CONFIG_IS_ENABLED(HUSH_PARSER)
+	U_BOOT_CMD_MKENT(hush, CONFIG_SYS_MAXARGS, 1, do_ut_hush, "", ""),
+#endif
 #ifdef CONFIG_CMD_LOADM
 	U_BOOT_CMD_MKENT(loadm, CONFIG_SYS_MAXARGS, 1, do_ut_loadm, "", ""),
 #endif
@@ -194,6 +197,9 @@ static char ut_help_text[] =
 #ifdef CONFIG_CONSOLE_TRUETYPE
 	"\nut font - font command"
 #endif
+#if CONFIG_IS_ENABLED(HUSH_PARSER)
+	"\nhush - Test hush behavior"
+#endif
 #ifdef CONFIG_CMD_LOADM
 	"\nloadm - loadm command parameters and loading memory blob"
 #endif
diff --git a/test/hush/Makefile b/test/hush/Makefile
new file mode 100644
index 0000000000..dfa2a92615
--- /dev/null
+++ b/test/hush/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2021
+# Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+
+obj-y += cmd_ut_hush.o
diff --git a/test/hush/cmd_ut_hush.c b/test/hush/cmd_ut_hush.c
new file mode 100644
index 0000000000..48a1adbf28
--- /dev/null
+++ b/test/hush/cmd_ut_hush.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#include <common.h>
+#include <command.h>
+#include <test/hush.h>
+#include <test/suites.h>
+#include <test/ut.h>
+
+int do_ut_hush(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct unit_test *tests = UNIT_TEST_SUITE_START(hush_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(hush_test);
+
+	return cmd_ut_category("hush", "hush_test_",
+			       tests, n_ents, argc, argv);
+}
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 02/23] test: hush: Test hush if/else
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 01/23] test: Add framework to test hush behavior Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 03/23] test/py: hush_if_test: Remove the test file Francis Laniel
                   ` (20 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Marek Vasut, Patrick Delaunay,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Linus Walleij, Rui Miguel Silva, Andrew Scull

As asked in commit 9c6bf1715f6a ("test/py: hush_if_test: Add tests to cover
octal/hex values"), this commit translates test_hush_if_test.py to a C test.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 test/hush/Makefile |   1 +
 test/hush/if.c     | 308 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 309 insertions(+)
 create mode 100644 test/hush/if.c

diff --git a/test/hush/Makefile b/test/hush/Makefile
index dfa2a92615..a3c9ae5106 100644
--- a/test/hush/Makefile
+++ b/test/hush/Makefile
@@ -4,3 +4,4 @@
 # Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
 
 obj-y += cmd_ut_hush.o
+obj-y += if.o
diff --git a/test/hush/if.c b/test/hush/if.c
new file mode 100644
index 0000000000..a57d88850f
--- /dev/null
+++ b/test/hush/if.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#include <common.h>
+#include <command.h>
+#include <env_attr.h>
+#include <test/hush.h>
+#include <test/ut.h>
+
+/*
+ * All tests will execute the following:
+ * if condition_to_test; then
+ *   true
+ * else
+ *   false
+ * fi
+ * If condition is true, command returns 1, 0 otherwise.
+ */
+const char *if_format = "if %s; then true; else false; fi";
+
+static int hush_test_if_base(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "true");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "false");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_base, 0);
+
+static int hush_test_if_basic_operators(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test aaa = aaa");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa = bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa != bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa != aaa");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa < bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test bbb < aaa");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test bbb > aaa");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa > bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -eq 123");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -eq 456");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -ne 456");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -ne 123");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -lt 456");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -lt 123");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 456 -lt 123");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -le 456");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -le 123");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 456 -le 123");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 456 -gt 123");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -gt 123");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -gt 456");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 456 -ge 123");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -ge 123");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 123 -ge 456");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_basic_operators, 0);
+
+static int hush_test_if_octal(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test 010 -eq 010");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 010 -eq 011");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 010 -ne 011");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 010 -ne 010");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_octal, 0);
+
+static int hush_test_if_hexadecimal(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -gt 0x2000001");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -gt 0x2000000");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -gt 0x1ffffff");
+	ut_assertok(run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_hexadecimal, 0);
+
+static int hush_test_if_mixed(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test 010 -eq 10");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 010 -ne 10");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0xa -eq 10");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0xa -eq 012");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 2000000 -gt 0x1ffffff");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -gt 1ffffff");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -lt 1ffffff");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -eq 2000000");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test 0x2000000 -ne 2000000");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test -z \"\"");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test -z \"aaa\"");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test -n \"aaa\"");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test -n \"\"");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_mixed, 0);
+
+static int hush_test_if_inverted(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test ! aaa = aaa");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! aaa = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! ! aaa = aaa");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! ! aaa = bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_inverted, 0);
+
+static int hush_test_if_binary(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test aaa != aaa -o bbb != bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa != aaa -o bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa = aaa -o bbb != bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa = aaa -o bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa != aaa -a bbb != bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa != aaa -a bbb = bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa = aaa -a bbb != bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test aaa = aaa -a bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_binary, 0);
+
+static int hush_test_if_inverted_binary(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	sprintf(if_formatted, if_format, "test ! aaa != aaa -o ! bbb != bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! aaa != aaa -o ! bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! aaa = aaa -o ! bbb != bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! aaa = aaa -o ! bbb = bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format,
+		"test ! ! aaa != aaa -o ! ! bbb != bbb");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format,
+		"test ! ! aaa != aaa -o ! ! bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format,
+		"test ! ! aaa = aaa -o ! ! bbb != bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test ! ! aaa = aaa -o ! ! bbb = bbb");
+	ut_assertok(run_command(if_formatted, 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_inverted_binary, 0);
+
+static int hush_test_if_z_operator(struct unit_test_state *uts)
+{
+	char if_formatted[128];
+
+	/* Deal with environment variable used during test. */
+	env_set("ut_var_nonexistent", NULL);
+	env_set("ut_var_exists", "1");
+
+	sprintf(if_formatted, if_format, "test -z \"$ut_var_nonexistent\"");
+	ut_assertok(run_command(if_formatted, 0));
+
+	sprintf(if_formatted, if_format, "test -z \"$ut_var_exists\"");
+	ut_asserteq(1, run_command(if_formatted, 0));
+
+	/* Clear the set environment variable. */
+	env_set("ut_var_exists", NULL);
+
+	return 0;
+}
+HUSH_TEST(hush_test_if_z_operator, 0);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 03/23] test/py: hush_if_test: Remove the test file
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 01/23] test: Add framework to test hush behavior Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 02/23] test: hush: Test hush if/else Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 04/23] test: hush: Test hush variable expansion Francis Laniel
                   ` (19 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Samuel Dionne-Riel, Robert Marko,
	Alexey Romanov, Fabrice Gasnier, Patrick Delaunay,
	Patrice Chotard, Hector Palacios, Marek Vasut, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Linus Walleij, Andrew Scull

ce40f44eb336 ("test: hush: Test hush if/else") translated this test to a C test,
so this python file is no more needed.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 test/py/tests/test_hush_if_test.py | 184 -----------------------------
 1 file changed, 184 deletions(-)
 delete mode 100644 test/py/tests/test_hush_if_test.py

diff --git a/test/py/tests/test_hush_if_test.py b/test/py/tests/test_hush_if_test.py
deleted file mode 100644
index 37c1608bb2..0000000000
--- a/test/py/tests/test_hush_if_test.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
-
-# Test operation of the "if" shell command.
-
-import os
-import os.path
-import pytest
-
-# TODO: These tests should be converted to a C test.
-# For more information please take a look at the thread
-# https://lists.denx.de/pipermail/u-boot/2019-October/388732.html
-
-pytestmark = pytest.mark.buildconfigspec('hush_parser')
-
-# The list of "if test" conditions to test.
-subtests = (
-    # Base if functionality.
-
-    ('true', True),
-    ('false', False),
-
-    # Basic operators.
-
-    ('test aaa = aaa', True),
-    ('test aaa = bbb', False),
-
-    ('test aaa != bbb', True),
-    ('test aaa != aaa', False),
-
-    ('test aaa < bbb', True),
-    ('test bbb < aaa', False),
-
-    ('test bbb > aaa', True),
-    ('test aaa > bbb', False),
-
-    ('test 123 -eq 123', True),
-    ('test 123 -eq 456', False),
-
-    ('test 123 -ne 456', True),
-    ('test 123 -ne 123', False),
-
-    ('test 123 -lt 456', True),
-    ('test 123 -lt 123', False),
-    ('test 456 -lt 123', False),
-
-    ('test 123 -le 456', True),
-    ('test 123 -le 123', True),
-    ('test 456 -le 123', False),
-
-    ('test 456 -gt 123', True),
-    ('test 123 -gt 123', False),
-    ('test 123 -gt 456', False),
-
-    ('test 456 -ge 123', True),
-    ('test 123 -ge 123', True),
-    ('test 123 -ge 456', False),
-
-    # Octal tests
-
-    ('test 010 -eq 010', True),
-    ('test 010 -eq 011', False),
-
-    ('test 010 -ne 011', True),
-    ('test 010 -ne 010', False),
-
-    # Hexadecimal tests
-
-    ('test 0x2000000 -gt 0x2000001', False),
-    ('test 0x2000000 -gt 0x2000000', False),
-    ('test 0x2000000 -gt 0x1ffffff', True),
-
-    # Mixed tests
-
-    ('test 010 -eq 10', False),
-    ('test 010 -ne 10', True),
-    ('test 0xa -eq 10', True),
-    ('test 0xa -eq 012', True),
-
-    ('test 2000000 -gt 0x1ffffff', False),
-    ('test 0x2000000 -gt 1ffffff', True),
-    ('test 0x2000000 -lt 1ffffff', False),
-    ('test 0x2000000 -eq 2000000', False),
-    ('test 0x2000000 -ne 2000000', True),
-
-    ('test -z ""', True),
-    ('test -z "aaa"', False),
-
-    ('test -n "aaa"', True),
-    ('test -n ""', False),
-
-    # Inversion of simple tests.
-
-    ('test ! aaa = aaa', False),
-    ('test ! aaa = bbb', True),
-    ('test ! ! aaa = aaa', True),
-    ('test ! ! aaa = bbb', False),
-
-    # Binary operators.
-
-    ('test aaa != aaa -o bbb != bbb', False),
-    ('test aaa != aaa -o bbb = bbb', True),
-    ('test aaa = aaa -o bbb != bbb', True),
-    ('test aaa = aaa -o bbb = bbb', True),
-
-    ('test aaa != aaa -a bbb != bbb', False),
-    ('test aaa != aaa -a bbb = bbb', False),
-    ('test aaa = aaa -a bbb != bbb', False),
-    ('test aaa = aaa -a bbb = bbb', True),
-
-    # Inversion within binary operators.
-
-    ('test ! aaa != aaa -o ! bbb != bbb', True),
-    ('test ! aaa != aaa -o ! bbb = bbb', True),
-    ('test ! aaa = aaa -o ! bbb != bbb', True),
-    ('test ! aaa = aaa -o ! bbb = bbb', False),
-
-    ('test ! ! aaa != aaa -o ! ! bbb != bbb', False),
-    ('test ! ! aaa != aaa -o ! ! bbb = bbb', True),
-    ('test ! ! aaa = aaa -o ! ! bbb != bbb', True),
-    ('test ! ! aaa = aaa -o ! ! bbb = bbb', True),
-)
-
-def exec_hush_if(u_boot_console, expr, result):
-    """Execute a shell "if" command, and validate its result."""
-
-    config = u_boot_console.config.buildconfig
-    maxargs = int(config.get('config_sys_maxargs', '0'))
-    args = len(expr.split(' ')) - 1
-    if args > maxargs:
-        u_boot_console.log.warning('CONFIG_SYS_MAXARGS too low; need ' +
-            str(args))
-        pytest.skip()
-
-    cmd = 'if ' + expr + '; then echo true; else echo false; fi'
-    response = u_boot_console.run_command(cmd)
-    assert response.strip() == str(result).lower()
-
-@pytest.mark.buildconfigspec('cmd_echo')
-@pytest.mark.parametrize('expr,result', subtests)
-def test_hush_if_test(u_boot_console, expr, result):
-    """Test a single "if test" condition."""
-
-    exec_hush_if(u_boot_console, expr, result)
-
-def test_hush_z(u_boot_console):
-    """Test the -z operator"""
-    u_boot_console.run_command('setenv ut_var_nonexistent')
-    u_boot_console.run_command('setenv ut_var_exists 1')
-    exec_hush_if(u_boot_console, 'test -z "$ut_var_nonexistent"', True)
-    exec_hush_if(u_boot_console, 'test -z "$ut_var_exists"', False)
-    u_boot_console.run_command('setenv ut_var_exists')
-
-# We might test this on real filesystems via UMS, DFU, 'save', etc.
-# Of those, only UMS currently allows file removal though.
-@pytest.mark.buildconfigspec('cmd_echo')
-@pytest.mark.boardspec('sandbox')
-def test_hush_if_test_host_file_exists(u_boot_console):
-    """Test the "if test -e" shell command."""
-
-    test_file = u_boot_console.config.result_dir + \
-        '/creating_this_file_breaks_u_boot_tests'
-
-    try:
-        os.unlink(test_file)
-    except:
-        pass
-    assert not os.path.exists(test_file)
-
-    expr = 'test -e hostfs - ' + test_file
-    exec_hush_if(u_boot_console, expr, False)
-
-    try:
-        with open(test_file, 'wb'):
-            pass
-        assert os.path.exists(test_file)
-
-        expr = 'test -e hostfs - ' + test_file
-        exec_hush_if(u_boot_console, expr, True)
-    finally:
-        os.unlink(test_file)
-
-    expr = 'test -e hostfs - ' + test_file
-    exec_hush_if(u_boot_console, expr, False)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 04/23] test: hush: Test hush variable expansion
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (2 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 03/23] test/py: hush_if_test: Remove the test file Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:46 ` [RFC PATCH v7 05/23] test: hush: Test hush commands list Francis Laniel
                   ` (18 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Alexey Romanov, Marek Vasut,
	Fabrice Gasnier, Hector Palacios, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Linus Walleij, Andrew Scull

Verifies shell variables are replaced by their values.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 test/hush/Makefile       |   1 +
 test/hush/dollar.c       | 167 +++++++++++++++++++++++++++++++++++++++
 test/py/tests/test_ut.py |   8 +-
 3 files changed, 175 insertions(+), 1 deletion(-)
 create mode 100644 test/hush/dollar.c

diff --git a/test/hush/Makefile b/test/hush/Makefile
index a3c9ae5106..feb4f71956 100644
--- a/test/hush/Makefile
+++ b/test/hush/Makefile
@@ -5,3 +5,4 @@
 
 obj-y += cmd_ut_hush.o
 obj-y += if.o
+obj-y += dollar.o
diff --git a/test/hush/dollar.c b/test/hush/dollar.c
new file mode 100644
index 0000000000..27feda2970
--- /dev/null
+++ b/test/hush/dollar.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#include <common.h>
+#include <command.h>
+#include <env_attr.h>
+#include <test/hush.h>
+#include <test/ut.h>
+
+static int hush_test_simple_dollar(struct unit_test_state *uts)
+{
+	console_record_reset_enable();
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	ut_assert_nextline("%s", "");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("echo ${dollar_foo}", 0));
+	ut_assert_nextline("%s", "");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_foo=bar", 0));
+
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("echo ${dollar_foo}", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_foo=\\$bar", 0));
+
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	ut_assert_nextline("$bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_foo='$bar'", 0));
+
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	ut_assert_nextline("$bar");
+	ut_assert_console_end();
+
+	ut_asserteq(1, run_command("dollar_foo=bar quux", 0));
+	/* Next line contains error message */
+	ut_assert_skipline();
+	ut_assert_console_end();
+
+	ut_asserteq(1, run_command("dollar_foo='bar quux", 0));
+	/* Next line contains error message */
+	ut_assert_skipline();
+	ut_assert_console_end();
+
+	ut_asserteq(1, run_command("dollar_foo=bar quux\"", 0));
+	/* Two next lines contain error message */
+	ut_assert_skipline();
+	ut_assert_skipline();
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_foo='bar \"quux'", 0));
+
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	/*
+	 * This one is buggy.
+	 * ut_assert_nextline("bar \"quux");
+	 * ut_assert_console_end();
+	 *
+	 * So, let's reset output:
+	 */
+	console_record_reset_enable();
+
+	ut_asserteq(1, run_command("dollar_foo=\"bar 'quux\"", 0));
+	/* Next line contains error message */
+	ut_assert_skipline();
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_foo='bar quux'", 0));
+	ut_assertok(run_command("echo $dollar_foo", 0));
+	ut_assert_nextline("bar quux");
+	ut_assert_console_end();
+
+	puts("Beware: this test set local variable dollar_foo and it cannot be unset!");
+
+	return 0;
+}
+HUSH_TEST(hush_test_simple_dollar, 0);
+
+static int hush_test_env_dollar(struct unit_test_state *uts)
+{
+	env_set("env_foo", "bar");
+	console_record_reset_enable();
+
+	ut_assertok(run_command("echo $env_foo", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("echo ${env_foo}", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	/* Environment variables have priority over local variable */
+	ut_assertok(run_command("env_foo=quux", 0));
+	ut_assertok(run_command("echo ${env_foo}", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	/* Clean up setting the variable */
+	env_set("env_foo", NULL);
+
+	puts("Beware: this test set local variable env_foo and it cannot be unset!");
+
+	return 0;
+}
+HUSH_TEST(hush_test_env_dollar, 0);
+
+static int hush_test_command_dollar(struct unit_test_state *uts)
+{
+	console_record_reset_enable();
+
+	ut_assertok(run_command("dollar_bar=\"echo bar\"", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("${dollar_bar}", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_bar=\"echo\nbar\"", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_bar='echo bar\n'", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_bar='echo bar\\n'", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("barn");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_bar='echo $bar'", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("$bar");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("dollar_quux=quux", 0));
+	ut_assertok(run_command("dollar_bar=\"echo $dollar_quux\"", 0));
+
+	ut_assertok(run_command("$dollar_bar", 0));
+	ut_assert_nextline("quux");
+	ut_assert_console_end();
+
+	puts("Beware: this test sets local variable dollar_bar and dollar_quux and they cannot be unset!");
+
+	return 0;
+}
+HUSH_TEST(hush_test_command_dollar, 0);
diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index e8c8a6d6bd..933e0320e8 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -342,5 +342,11 @@ def test_ut(u_boot_console, ut_subtest):
             execute command 'ut foo bar'
     """
 
-    output = u_boot_console.run_command('ut ' + ut_subtest)
+    if ut_subtest == 'hush hush_test_simple_dollar':
+        # ut hush hush_test_simple_dollar prints "Unknown command" on purpose.
+        with u_boot_console.disable_check('unknown_command'):
+            output = u_boot_console.run_command('ut ' + ut_subtest)
+        assert('Unknown command \'quux\' - try \'help\'' in output)
+    else:
+        output = u_boot_console.run_command('ut ' + ut_subtest)
     assert output.endswith('Failures: 0')
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 05/23] test: hush: Test hush commands list
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (3 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 04/23] test: hush: Test hush variable expansion Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 06/23] test: hush: Test hush loops Francis Laniel
                   ` (17 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Patrick Delaunay, Marek Vasut, Fabrice Gasnier,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Linus Walleij, Andrew Scull

Verifies behavior of commands separated by ';', '&&' and '||'.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 test/hush/Makefile |  1 +
 test/hush/list.c   | 79 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 80 insertions(+)
 create mode 100644 test/hush/list.c

diff --git a/test/hush/Makefile b/test/hush/Makefile
index feb4f71956..ff4fe7700b 100644
--- a/test/hush/Makefile
+++ b/test/hush/Makefile
@@ -6,3 +6,4 @@
 obj-y += cmd_ut_hush.o
 obj-y += if.o
 obj-y += dollar.o
+obj-y += list.o
diff --git a/test/hush/list.c b/test/hush/list.c
new file mode 100644
index 0000000000..052cf2783c
--- /dev/null
+++ b/test/hush/list.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#include <common.h>
+#include <command.h>
+#include <env_attr.h>
+#include <test/hush.h>
+#include <test/ut.h>
+
+static int hush_test_semicolon(struct unit_test_state *uts)
+{
+	/* A; B = B truth table. */
+	ut_asserteq(1, run_command("false; false", 0));
+	ut_assertok(run_command("false; true", 0));
+	ut_assertok(run_command("true; true", 0));
+	ut_asserteq(1, run_command("true; false", 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_semicolon, 0);
+
+static int hush_test_and(struct unit_test_state *uts)
+{
+	/* A && B truth table. */
+	ut_asserteq(1, run_command("false && false", 0));
+	ut_asserteq(1, run_command("false && true", 0));
+	ut_assertok(run_command("true && true", 0));
+	ut_asserteq(1, run_command("true && false", 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_and, 0);
+
+static int hush_test_or(struct unit_test_state *uts)
+{
+	/* A || B truth table. */
+	ut_asserteq(1, run_command("false || false", 0));
+	ut_assertok(run_command("false || true", 0));
+	ut_assertok(run_command("true || true", 0));
+	ut_assertok(run_command("true || false", 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_or, 0);
+
+static int hush_test_and_or(struct unit_test_state *uts)
+{
+	/* A && B || C truth table. */
+	ut_asserteq(1, run_command("false && false || false", 0));
+	ut_asserteq(1, run_command("false && false || true", 0));
+	ut_asserteq(1, run_command("false && true || true", 0));
+	ut_asserteq(1, run_command("false && true || false", 0));
+	ut_assertok(run_command("true && true || false", 0));
+	ut_asserteq(1, run_command("true && false || false", 0));
+	ut_assertok(run_command("true && false || true", 0));
+	ut_assertok(run_command("true && true || true", 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_and_or, 0);
+
+static int hush_test_or_and(struct unit_test_state *uts)
+{
+	/* A || B && C truth table. */
+	ut_asserteq(1, run_command("false || false && false", 0));
+	ut_asserteq(1, run_command("false || false && true", 0));
+	ut_assertok(run_command("false || true && true", 0));
+	ut_asserteq(1, run_command("false || true && false", 0));
+	ut_assertok(run_command("true || true && false", 0));
+	ut_assertok(run_command("true || false && false", 0));
+	ut_assertok(run_command("true || false && true", 0));
+	ut_assertok(run_command("true || true && true", 0));
+
+	return 0;
+}
+HUSH_TEST(hush_test_or_and, 0);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 06/23] test: hush: Test hush loops
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (4 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 05/23] test: hush: Test hush commands list Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 07/23] cli: Add Busybox upstream hush.c file Francis Laniel
                   ` (16 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Sughosh Ganu, Samuel Dionne-Riel,
	Alexey Romanov, Marek Vasut, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Linus Walleij, Rui Miguel Silva, Andrew Scull

The added tests verifies correct behavior of for, while and until loops.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 test/hush/Makefile |  1 +
 test/hush/loop.c   | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+)
 create mode 100644 test/hush/loop.c

diff --git a/test/hush/Makefile b/test/hush/Makefile
index ff4fe7700b..a2d98815e5 100644
--- a/test/hush/Makefile
+++ b/test/hush/Makefile
@@ -7,3 +7,4 @@ obj-y += cmd_ut_hush.o
 obj-y += if.o
 obj-y += dollar.o
 obj-y += list.o
+obj-y += loop.o
diff --git a/test/hush/loop.c b/test/hush/loop.c
new file mode 100644
index 0000000000..ca777e38fe
--- /dev/null
+++ b/test/hush/loop.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+#include <common.h>
+#include <command.h>
+#include <env_attr.h>
+#include <test/hush.h>
+#include <test/ut.h>
+
+static int hush_test_for(struct unit_test_state *uts)
+{
+	console_record_reset_enable();
+
+	ut_assertok(run_command("for loop_i in foo bar quux quux; do echo $loop_i; done", 0));
+	ut_assert_nextline("foo");
+	ut_assert_nextline("bar");
+	ut_assert_nextline("quux");
+	ut_assert_nextline("quux");
+	ut_assert_console_end();
+
+	puts("Beware: this test set local variable loop_i and it cannot be unset!");
+
+	return 0;
+}
+HUSH_TEST(hush_test_for, 0);
+
+static int hush_test_while(struct unit_test_state *uts)
+{
+	console_record_reset_enable();
+
+	/* Exit status is that of test, so 1 since test is false to quit the loop. */
+	ut_asserteq(1, run_command("while test -z \"$loop_foo\"; do echo bar; loop_foo=quux; done", 0));
+	ut_assert_nextline("bar");
+	ut_assert_console_end();
+
+	puts("Beware: this test set local variable loop_foo and it cannot be unset!");
+
+	return 0;
+}
+HUSH_TEST(hush_test_while, 0);
+
+static int hush_test_until(struct unit_test_state *uts)
+{
+	console_record_reset_enable();
+	env_set("loop_bar", "bar");
+
+	/*
+	 * WARNING We have to use environment variable because it is not possible
+	 * resetting local variable.
+	 */
+	ut_assertok(run_command("until test -z \"$loop_bar\"; do echo quux; setenv loop_bar; done", 0));
+	ut_assert_nextline("quux");
+	ut_assert_console_end();
+
+	/*
+	 * Loop normally resets foo environment variable, but we reset it here in
+	 * case the test failed.
+	 */
+	env_set("loop_bar", NULL);
+	return 0;
+}
+HUSH_TEST(hush_test_until, 0);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 07/23] cli: Add Busybox upstream hush.c file
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (5 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 06/23] test: hush: Test hush loops Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 08/23] cli: Port Busybox 2021 hush to U-Boot Francis Laniel
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Robert Marko, Linus Walleij,
	Alexey Romanov, Patrice Chotard, Fabrice Gasnier, Marek Vasut,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

This commit adds new file cli_hush_2021_upstream.c, it is a copy of Busybox hush
file it was of time to Busybox commit 37460f5da.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_upstream.c | 12162 +++++++++++++++++++++++++++++++++++
 1 file changed, 12162 insertions(+)
 create mode 100644 common/cli_hush_upstream.c

diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
new file mode 100644
index 0000000000..c970d9097e
--- /dev/null
+++ b/common/cli_hush_upstream.c
@@ -0,0 +1,12162 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * A prototype Bourne shell grammar parser.
+ * Intended to follow the original Thompson and Ritchie
+ * "small and simple is beautiful" philosophy, which
+ * incidentally is a good match to today's BusyBox.
+ *
+ * Copyright (C) 2000,2001  Larry Doolittle <larry@doolittle.boa.org>
+ * Copyright (C) 2008,2009  Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * Credits:
+ *      The parser routines proper are all original material, first
+ *      written Dec 2000 and Jan 2001 by Larry Doolittle.  The
+ *      execution engine, the builtins, and much of the underlying
+ *      support has been adapted from busybox-0.49pre's lash, which is
+ *      Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *      written by Erik Andersen <andersen@codepoet.org>.  That, in turn,
+ *      is based in part on ladsh.c, by Michael K. Johnson and Erik W.
+ *      Troan, which they placed in the public domain.  I don't know
+ *      how much of the Johnson/Troan code has survived the repeated
+ *      rewrites.
+ *
+ * Other credits:
+ *      o_addchr derived from similar w_addchar function in glibc-2.2.
+ *      parse_redirect, redirect_opt_num, and big chunks of main
+ *      and many builtins derived from contributions by Erik Andersen.
+ *      Miscellaneous bugfixes from Matt Kraai.
+ *
+ * There are two big (and related) architecture differences between
+ * this parser and the lash parser.  One is that this version is
+ * actually designed from the ground up to understand nearly all
+ * of the Bourne grammar.  The second, consequential change is that
+ * the parser and input reader have been turned inside out.  Now,
+ * the parser is in control, and asks for input as needed.  The old
+ * way had the input reader in control, and it asked for parsing to
+ * take place as needed.  The new way makes it much easier to properly
+ * handle the recursion implicit in the various substitutions, especially
+ * across continuation lines.
+ *
+ * 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
+ *      "command" missing features:
+ *          command -p CMD: run CMD using default $PATH
+ *              (can use this to override standalone shell as well?)
+ *          command BLTIN: disables special-ness (e.g. errors do not abort)
+ *          command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
+ *      builtins mandated by standards we don't support:
+ *          [un]alias, fc:
+ *          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 >&
+ *      reserved words: function select
+ *      advanced test: [[ ]]
+ *      process substitution: <(list) and >(list)
+ *      let EXPR [EXPR...]
+ *          Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION)
+ *          If the last arg evaluates to 0, let returns 1; 0 otherwise.
+ *          NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used)
+ *      ((EXPR))
+ *          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
+ *
+ * Status of [[ support:
+ * [[ args ]] are CMD_SINGLEWORD_NOGLOB:
+ *   v='a b'; [[ $v = 'a b' ]]; echo 0:$?
+ *   [[ /bin/n* ]]; echo 0:$?
+ *   = is glob match operator, not equality operator: STR = GLOB
+ *   == same as =
+ *   =~ is regex match operator: STR =~ REGEX
+ * TODO:
+ * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
+ * in word = GLOB, quoting should be significant on char-by-char basis: a*cd"*"
+ */
+//config:config HUSH
+//config:	bool "hush (68 kb)"
+//config:	default y
+//config:	select SHELL_HUSH
+//config:	help
+//config:	hush is a small shell. It handles the normal flow control
+//config:	constructs such as if/then/elif/else/fi, for/in/do/done, while loops,
+//config:	case/esac. Redirections, here documents, $((arithmetic))
+//config:	and functions are supported.
+//config:
+//config:	It will compile and work on no-mmu systems.
+//config:
+//config:	It does not handle select, aliases, tilde expansion,
+//config:	&>file and >&file redirection of stdout+stderr.
+//config:
+// This option is visible (has a description) to make it possible to select
+// a "scripted" applet (such as NOLOGIN) but avoid selecting any shells:
+//config:config SHELL_HUSH
+//config:	bool "Internal shell for embedded script support"
+//config:	default n
+//config:
+//config:# hush options
+//config:# It's only needed to get "nice" menuconfig indenting.
+//config:if SHELL_HUSH || HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:
+//config:config HUSH_BASH_COMPAT
+//config:	bool "bash-compatible extensions"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_BRACE_EXPANSION
+//config:	bool "Brace expansion"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:	help
+//config:	Enable {abc,def} extension.
+//config:
+//config:config HUSH_BASH_SOURCE_CURDIR
+//config:	bool "'source' and '.' builtins search current directory after $PATH"
+//config:	default n   # do not encourage non-standard behavior
+//config:	depends on HUSH_BASH_COMPAT
+//config:	help
+//config:	This is not compliant with standards. Avoid if possible.
+//config:
+//config:config HUSH_LINENO_VAR
+//config:	bool "$LINENO variable (bashism)"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_INTERACTIVE
+//config:	bool "Interactive mode"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable interactive mode (prompt and command editing).
+//config:	Without this, hush simply reads and executes commands
+//config:	from stdin just like a shell script from a file.
+//config:	No prompt, no PS1/PS2 magic shell variables.
+//config:
+//config:config HUSH_SAVEHISTORY
+//config:	bool "Save command history to .hush_history"
+//config:	default y
+//config:	depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY
+//config:
+//config:config HUSH_JOB
+//config:	bool "Job control"
+//config:	default y
+//config:	depends on HUSH_INTERACTIVE
+//config:	help
+//config:	Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
+//config:	command (not entire shell), fg/bg builtins work. Without this option,
+//config:	"cmd &" still works by simply spawning a process and immediately
+//config:	prompting for next command (or executing next command in a script),
+//config:	but no separate process group is formed.
+//config:
+//config:config HUSH_TICK
+//config:	bool "Support command substitution"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable `command` and $(command).
+//config:
+//config:config HUSH_IF
+//config:	bool "Support if/then/elif/else/fi"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_LOOPS
+//config:	bool "Support for, while and until loops"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_CASE
+//config:	bool "Support case ... esac statement"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable case ... esac statement. +400 bytes.
+//config:
+//config:config HUSH_FUNCTIONS
+//config:	bool "Support funcname() { commands; } syntax"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable support for shell functions. +800 bytes.
+//config:
+//config:config HUSH_LOCAL
+//config:	bool "local builtin"
+//config:	default y
+//config:	depends on HUSH_FUNCTIONS
+//config:	help
+//config:	Enable support for local variables in functions.
+//config:
+//config:config HUSH_RANDOM_SUPPORT
+//config:	bool "Pseudorandom generator and $RANDOM variable"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable pseudorandom generator and dynamic variable "$RANDOM".
+//config:	Each read of "$RANDOM" will generate a new pseudorandom value.
+//config:
+//config:config HUSH_MODE_X
+//config:	bool "Support 'hush -x' option and 'set -x' command"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:	help
+//config:	This instructs hush to print commands before execution.
+//config:	Adds ~300 bytes.
+//config:
+//config:config HUSH_ECHO
+//config:	bool "echo builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_PRINTF
+//config:	bool "printf builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_TEST
+//config:	bool "test builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_HELP
+//config:	bool "help builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_EXPORT
+//config:	bool "export builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_EXPORT_N
+//config:	bool "Support 'export -n' option"
+//config:	default y
+//config:	depends on HUSH_EXPORT
+//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:	depends on SHELL_HUSH
+//config:	help
+//config:	Enable support for read-only variables.
+//config:
+//config:config HUSH_KILL
+//config:	bool "kill builtin (supports kill %jobspec)"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_WAIT
+//config:	bool "wait builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_COMMAND
+//config:	bool "command builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_TRAP
+//config:	bool "trap builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_TYPE
+//config:	bool "type builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_TIMES
+//config:	bool "times builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_READ
+//config:	bool "read builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_SET
+//config:	bool "set builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_UNSET
+//config:	bool "unset builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_ULIMIT
+//config:	bool "ulimit builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_UMASK
+//config:	bool "umask builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_GETOPTS
+//config:	bool "getopts builtin"
+//config:	default y
+//config:	depends on SHELL_HUSH
+//config:
+//config:config HUSH_MEMLEAK
+//config:	bool "memleak builtin (debugging)"
+//config:	default n
+//config:	depends on SHELL_HUSH
+//config:
+//config:endif # hush options
+
+//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
+//                       APPLET_ODDNAME:name  main  location    suid_type     help
+//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))
+
+//kbuild:lib-$(CONFIG_SHELL_HUSH) += hush.o match.o shell_common.o
+//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
+
+/* -i (interactive) is also accepted,
+ * but does nothing, therefore not shown in help.
+ * NOMMU-specific options are not meant to be used by users,
+ * therefore we don't show them either.
+ */
+//usage:#define hush_trivial_usage
+//usage:	"[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE [ARGS] | -s [ARGS]]"
+//usage:#define hush_full_usage "\n\n"
+//usage:	"Unix shell interpreter"
+
+#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
+	|| defined(__APPLE__) \
+    )
+# include <malloc.h>   /* for malloc_trim */
+#endif
+#include <glob.h>
+/* #include <dmalloc.h> */
+#if ENABLE_HUSH_CASE
+# include <fnmatch.h>
+#endif
+#include <sys/times.h>
+#include <sys/utsname.h> /* for setting $HOSTNAME */
+
+#include "busybox.h"  /* for APPLET_IS_NOFORK/NOEXEC */
+#include "unicode.h"
+#include "shell_common.h"
+#include "math.h"
+#include "match.h"
+#if ENABLE_HUSH_RANDOM_SUPPORT
+# include "random.h"
+#else
+# define CLEAR_RANDOM_T(rnd) ((void)0)
+#endif
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+#ifndef F_DUPFD_CLOEXEC
+# define F_DUPFD_CLOEXEC F_DUPFD
+#endif
+
+#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !(ENABLE_ASH || ENABLE_SH_IS_ASH || ENABLE_BASH_IS_ASH)
+# include "embedded_scripts.h"
+#else
+# define NUM_SCRIPTS 0
+#endif
+
+/* So far, all bash compat is controlled by one config option */
+/* 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_SOURCE        ENABLE_HUSH_BASH_COMPAT
+#define BASH_DOLLAR_SQUOTE ENABLE_HUSH_BASH_COMPAT
+#define BASH_HOSTNAME_VAR  ENABLE_HUSH_BASH_COMPAT
+#define BASH_EPOCH_VARS    ENABLE_HUSH_BASH_COMPAT
+#define BASH_TEST2         (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
+#define BASH_READ_D        ENABLE_HUSH_BASH_COMPAT
+
+
+/* Build knobs */
+#define LEAK_HUNTING 0
+#define BUILD_AS_NOMMU 0
+/* Enable/disable sanity checks. Ok to enable in production,
+ * only adds a bit of bloat. Set to >1 to get non-production level verbosity.
+ * Keeping 1 for now even in released versions.
+ */
+#define HUSH_DEBUG 1
+/* Slightly bigger (+200 bytes), but faster hush.
+ * So far it only enables a trick with counting SIGCHLDs and forks,
+ * which allows us to do fewer waitpid's.
+ * (we can detect a case where neither forks were done nor SIGCHLDs happened
+ * and therefore waitpid will return the same result as last time)
+ */
+#define ENABLE_HUSH_FAST 0
+/* TODO: implement simplified code for users which do not need ${var%...} ops
+ * So far ${var%...} ops are always enabled:
+ */
+#define ENABLE_HUSH_DOLLAR_OPS 1
+
+
+#if BUILD_AS_NOMMU
+# undef BB_MMU
+# undef USE_FOR_NOMMU
+# undef USE_FOR_MMU
+# define BB_MMU 0
+# define USE_FOR_NOMMU(...) __VA_ARGS__
+# define USE_FOR_MMU(...)
+#endif
+
+#include "NUM_APPLETS.h"
+#if NUM_APPLETS == 1
+/* STANDALONE does not make sense, and won't compile */
+# undef CONFIG_FEATURE_SH_STANDALONE
+# undef ENABLE_FEATURE_SH_STANDALONE
+# undef IF_FEATURE_SH_STANDALONE
+# undef IF_NOT_FEATURE_SH_STANDALONE
+# define ENABLE_FEATURE_SH_STANDALONE 0
+# define IF_FEATURE_SH_STANDALONE(...)
+# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+#endif
+
+#if !ENABLE_HUSH_INTERACTIVE
+# undef ENABLE_FEATURE_EDITING
+# define ENABLE_FEATURE_EDITING 0
+# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
+# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
+#endif
+
+/* Do we support ANY keywords? */
+#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+# define HAS_KEYWORDS 1
+# define IF_HAS_KEYWORDS(...) __VA_ARGS__
+# define IF_HAS_NO_KEYWORDS(...)
+#else
+# define HAS_KEYWORDS 0
+# define IF_HAS_KEYWORDS(...)
+# define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
+#endif
+
+/* If you comment out one of these below, it will be #defined later
+ * to perform debug printfs to stderr: */
+#define debug_printf(...)         do {} while (0)
+/* Finer-grained debug switches */
+#define debug_printf_parse(...)   do {} while (0)
+#define debug_printf_heredoc(...) do {} while (0)
+#define debug_print_tree(a, b)    do {} while (0)
+#define debug_printf_exec(...)    do {} while (0)
+#define debug_printf_env(...)     do {} while (0)
+#define debug_printf_jobs(...)    do {} while (0)
+#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_prompt(...)  do {} while (0)
+#define debug_printf_clean(...)   do {} while (0)
+
+#define ERR_PTR ((void*)(long)1)
+
+#define JOB_STATUS_FORMAT    "[%u] %-22s %.40s\n"
+
+#define _SPECIAL_VARS_STR     "_*@$!?#-"
+#define SPECIAL_VARS_STR     ("_*@$!?#-" + 1)
+#define NUMERIC_SPECVARS_STR ("_*@$!?#-" + 3)
+#if BASH_PATTERN_SUBST
+/* Support / and // replace ops */
+/* Note that // is stored as \ in "encoded" string representation */
+# define VAR_ENCODED_SUBST_OPS      "\\/%#:-=+?"
+# define VAR_SUBST_OPS             ("\\/%#:-=+?" + 1)
+# define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5)
+#else
+# define VAR_ENCODED_SUBST_OPS      "%#:-=+?"
+# define VAR_SUBST_OPS              "%#:-=+?"
+# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
+#endif
+
+#define SPECIAL_VAR_SYMBOL_STR "\3"
+#define SPECIAL_VAR_SYMBOL       3
+/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
+#define SPECIAL_VAR_QUOTED_SVS   1
+
+struct variable;
+
+static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
+
+/* This supports saving pointers malloced in vfork child,
+ * to be freed in the parent.
+ */
+#if !BB_MMU
+typedef struct nommu_save_t {
+	struct variable *old_vars;
+	char **argv;
+	char **argv_from_re_execing;
+} nommu_save_t;
+#endif
+
+enum {
+	RES_NONE  = 0,
+#if ENABLE_HUSH_IF
+	RES_IF    ,
+	RES_THEN  ,
+	RES_ELIF  ,
+	RES_ELSE  ,
+	RES_FI    ,
+#endif
+#if ENABLE_HUSH_LOOPS
+	RES_FOR   ,
+	RES_WHILE ,
+	RES_UNTIL ,
+	RES_DO    ,
+	RES_DONE  ,
+#endif
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+	RES_IN    ,
+#endif
+#if ENABLE_HUSH_CASE
+	RES_CASE  ,
+	/* three pseudo-keywords support contrived "case" syntax: */
+	RES_CASE_IN,   /* "case ... IN", turns into RES_MATCH when IN is observed */
+	RES_MATCH ,    /* "word)" */
+	RES_CASE_BODY, /* "this command is inside CASE" */
+	RES_ESAC  ,
+#endif
+	RES_XXXX  ,
+	RES_SNTX
+};
+
+typedef struct o_string {
+	char *data;
+	int length; /* position where data is appended */
+	int maxlen;
+	int o_expflags;
+	/* At least some part of the string was inside '' or "",
+	 * possibly empty one: word"", wo''rd etc. */
+	smallint has_quoted_part;
+	smallint has_empty_slot;
+	smallint ended_in_ifs;
+} o_string;
+enum {
+	EXP_FLAG_SINGLEWORD     = 0x80, /* must be 0x80 */
+	EXP_FLAG_GLOB           = 0x2,
+	/* Protect newly added chars against globbing
+	 * by prepending \ to *, ?, [, \ */
+	EXP_FLAG_ESC_GLOB_CHARS = 0x1,
+};
+/* Used for initialization: o_string foo = NULL_O_STRING; */
+#define NULL_O_STRING { NULL }
+
+#ifndef debug_printf_parse
+static const char *const assignment_flag[] = {
+	"MAYBE_ASSIGNMENT",
+	"DEFINITELY_ASSIGNMENT",
+	"NOT_ASSIGNMENT",
+	"WORD_IS_KEYWORD",
+};
+#endif
+
+/* We almost can use standard FILE api, but we need an ability to move
+ * its fd when redirects coincide with it. No api exists for that
+ * (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902).
+ * HFILE is our internal alternative. Only supports reading.
+ * Since we now can, we incorporate linked list of all opened HFILEs
+ * into the struct (used to be a separate mini-list).
+ */
+typedef struct HFILE {
+	char *cur;
+	char *end;
+	struct HFILE *next_hfile;
+	int fd;
+	char buf[1024];
+} HFILE;
+
+typedef struct in_str {
+	const char *p;
+	int peek_buf[2];
+	int last_char;
+	HFILE *file;
+} in_str;
+
+/* The descrip member of this structure is only used to make
+ * debugging output pretty */
+static const struct {
+	int32_t mode;
+	signed char default_fd;
+	char descrip[3];
+} redir_table[] ALIGN4 = {
+	{ O_RDONLY,                  0, "<"  },
+	{ O_CREAT|O_TRUNC|O_WRONLY,  1, ">"  },
+	{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
+	{ O_CREAT|O_RDWR,            1, "<>" },
+	{ O_RDONLY,                  0, "<<" },
+/* Should not be needed. Bogus default_fd helps in debugging */
+/*	{ O_RDONLY,                 77, "<<" }, */
+};
+
+struct redir_struct {
+	struct redir_struct *next;
+	char *rd_filename;          /* filename */
+	int rd_fd;                  /* fd to redirect */
+	/* fd to redirect to, or -3 if rd_fd is to be closed (n>&-) */
+	int rd_dup;
+	smallint rd_type;           /* (enum redir_type) */
+	/* note: for heredocs, rd_filename contains heredoc delimiter,
+	 * and subsequently heredoc itself; and rd_dup is a bitmask:
+	 * bit 0: do we need to trim leading tabs?
+	 * bit 1: is heredoc quoted (<<'delim' syntax) ?
+	 */
+};
+typedef enum redir_type {
+	REDIRECT_INPUT     = 0,
+	REDIRECT_OVERWRITE = 1,
+	REDIRECT_APPEND    = 2,
+	REDIRECT_IO        = 3,
+	REDIRECT_HEREDOC   = 4,
+	REDIRECT_HEREDOC2  = 5, /* REDIRECT_HEREDOC after heredoc is loaded */
+
+	REDIRFD_CLOSE      = -3,
+	REDIRFD_SYNTAX_ERR = -2,
+	REDIRFD_TO_FILE    = -1,
+	/* otherwise, rd_fd is redirected to rd_dup */
+
+	HEREDOC_SKIPTABS = 1,
+	HEREDOC_QUOTED   = 2,
+} redir_type;
+
+
+struct command {
+	pid_t pid;                  /* 0 if exited */
+	unsigned assignment_cnt;    /* how many argv[i] are assignments? */
+#if ENABLE_HUSH_LINENO_VAR
+	unsigned lineno;
+#endif
+	smallint cmd_type;          /* CMD_xxx */
+#define CMD_NORMAL   0
+#define CMD_SUBSHELL 1
+#if BASH_TEST2
+/* used for "[[ EXPR ]]" */
+# define CMD_TEST2_SINGLEWORD_NOGLOB 2
+#endif
+#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
+/* used to prevent word splitting and globbing in "export v=t*" */
+# define CMD_SINGLEWORD_NOGLOB 3
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+# define CMD_FUNCDEF 4
+#endif
+
+	smalluint cmd_exitcode;
+	/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
+	struct pipe *group;
+#if !BB_MMU
+	char *group_as_string;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+	struct function *child_func;
+/* This field is used to prevent a bug here:
+ * while...do f1() {a;}; f1; f1() {b;}; f1; done
+ * When we execute "f1() {a;}" cmd, we create new function and clear
+ * cmd->group, cmd->group_as_string, cmd->argv[0].
+ * When we execute "f1() {b;}", we notice that f1 exists,
+ * and that its "parent cmd" struct is still "alive",
+ * we put those fields back into cmd->xxx
+ * (struct function has ->parent_cmd ptr to facilitate that).
+ * When we loop back, we can execute "f1() {a;}" again and set f1 correctly.
+ * Without this trick, loop would execute a;b;b;b;...
+ * instead of correct sequence a;b;a;b;...
+ * When command is freed, it severs the link
+ * (sets ->child_func->parent_cmd to NULL).
+ */
+#endif
+	char **argv;                /* command name and arguments */
+/* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
+ * and on execution these are substituted with their values.
+ * Substitution can make _several_ words out of one argv[n]!
+ * Example: argv[0]=='.^C*^C.' here: echo .$*.
+ * References of the form ^C`cmd arg^C are `cmd arg` substitutions.
+ */
+	struct redir_struct *redirects; /* I/O redirections */
+};
+/* Is there anything in this command at all? */
+#define IS_NULL_CMD(cmd) \
+	(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
+
+struct pipe {
+	struct pipe *next;
+	int num_cmds;               /* total number of commands in pipe */
+	int alive_cmds;             /* number of commands running (not exited) */
+	int stopped_cmds;           /* number of commands alive, but stopped */
+#if ENABLE_HUSH_JOB
+	unsigned jobid;             /* job number */
+	pid_t pgrp;                 /* process group ID for the job */
+	char *cmdtext;              /* name of job */
+#endif
+	struct command *cmds;       /* array of commands in pipe */
+	smallint followup;          /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
+	IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
+	IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
+};
+typedef enum pipe_style {
+	PIPE_SEQ = 0,
+	PIPE_AND = 1,
+	PIPE_OR  = 2,
+	PIPE_BG  = 3,
+} pipe_style;
+/* Is there anything in this pipe at all? */
+#define IS_NULL_PIPE(pi) \
+	((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
+
+/* This holds pointers to the various results of parsing */
+struct parse_context {
+	/* linked list of pipes */
+	struct pipe *list_head;
+	/* last pipe (being constructed right now) */
+	struct pipe *pipe;
+	/* last command in pipe (being constructed right now) */
+	struct command *command;
+	/* last redirect in command->redirects list */
+	struct redir_struct *pending_redirect;
+	o_string word;
+#if !BB_MMU
+	o_string as_string;
+#endif
+	smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */
+#if HAS_KEYWORDS
+	smallint ctx_res_w;
+	smallint ctx_inverted; /* "! cmd | cmd" */
+#if ENABLE_HUSH_CASE
+	smallint ctx_dsemicolon; /* ";;" seen */
+#endif
+	/* bitmask of FLAG_xxx, for figuring out valid reserved words */
+	int old_flag;
+	/* group we are enclosed in:
+	 * example: "if pipe1; pipe2; then pipe3; fi"
+	 * when we see "if" or "then", we malloc and copy current context,
+	 * and make ->stack point to it. then we parse pipeN.
+	 * when closing "then" / fi" / whatever is found,
+	 * we move list_head into ->stack->command->group,
+	 * copy ->stack into current context, and delete ->stack.
+	 * (parsing of { list } and ( list ) doesn't use this method)
+	 */
+	struct parse_context *stack;
+#endif
+};
+enum {
+	MAYBE_ASSIGNMENT      = 0,
+	DEFINITELY_ASSIGNMENT = 1,
+	NOT_ASSIGNMENT        = 2,
+	/* Not an assignment, but next word may be: "if v=xyz cmd;" */
+	WORD_IS_KEYWORD       = 3,
+};
+
+/* On program start, environ points to initial environment.
+ * putenv adds new pointers into it, unsetenv removes them.
+ * Neither of these (de)allocates the strings.
+ * setenv allocates new strings in malloc space and does putenv,
+ * and thus setenv is unusable (leaky) for shell's purposes */
+#define setenv(...) setenv_is_leaky_dont_use()
+struct variable {
+	struct variable *next;
+	char *varstr;        /* points to "name=" portion */
+	int max_len;         /* if > 0, name is part of initial env; else name is malloced */
+	uint16_t var_nest_level;
+	smallint flg_export; /* putenv should be done on this var */
+	smallint flg_read_only;
+};
+
+enum {
+	BC_BREAK = 1,
+	BC_CONTINUE = 2,
+};
+
+#if ENABLE_HUSH_FUNCTIONS
+struct function {
+	struct function *next;
+	char *name;
+	struct command *parent_cmd;
+	struct pipe *body;
+# if !BB_MMU
+	char *body_as_string;
+# endif
+};
+#endif
+
+
+/* set -/+o OPT support. (TODO: make it optional)
+ * bash supports the following opts:
+ * allexport       off
+ * braceexpand     on
+ * emacs           on
+ * errexit         off
+ * errtrace        off
+ * functrace       off
+ * hashall         on
+ * histexpand      off
+ * history         on
+ * ignoreeof       off
+ * interactive-comments    on
+ * keyword         off
+ * monitor         on
+ * noclobber       off
+ * noexec          off
+ * noglob          off
+ * nolog           off
+ * notify          off
+ * nounset         off
+ * onecmd          off
+ * physical        off
+ * pipefail        off
+ * posix           off
+ * privileged      off
+ * verbose         off
+ * vi              off
+ * xtrace          off
+ */
+static const char o_opt_strings[] ALIGN1 =
+	"pipefail\0"
+	"noexec\0"
+	"errexit\0"
+#if ENABLE_HUSH_MODE_X
+	"xtrace\0"
+#endif
+	;
+enum {
+	OPT_O_PIPEFAIL,
+	OPT_O_NOEXEC,
+	OPT_O_ERREXIT,
+#if ENABLE_HUSH_MODE_X
+	OPT_O_XTRACE,
+#endif
+	NUM_OPT_O
+};
+
+/* "Globals" within this file */
+/* Sorted roughly by size (smaller offsets == smaller code) */
+struct globals {
+	/* interactive_fd != 0 means we are an interactive shell.
+	 * If we are, then saved_tty_pgrp can also be != 0, meaning
+	 * that controlling tty is available. With saved_tty_pgrp == 0,
+	 * job control still works, but terminal signals
+	 * (^C, ^Z, ^Y, ^\) won't work at all, and background
+	 * process groups can only be created with "cmd &".
+	 * With saved_tty_pgrp != 0, hush will use tcsetpgrp()
+	 * to give tty to the foreground process group,
+	 * and will take it back when the group is stopped (^Z)
+	 * or killed (^C).
+	 */
+#if ENABLE_HUSH_INTERACTIVE
+	/* 'interactive_fd' is a fd# open to ctty, if we have one
+	 * _AND_ if we decided to act interactively */
+	int interactive_fd;
+	IF_NOT_FEATURE_EDITING_FANCY_PROMPT(char *PS1;)
+# define G_interactive_fd (G.interactive_fd)
+#else
+# define G_interactive_fd 0
+#endif
+#if ENABLE_FEATURE_EDITING
+	line_input_t *line_input_state;
+#endif
+	pid_t root_pid;
+	pid_t root_ppid;
+	pid_t last_bg_pid;
+#if ENABLE_HUSH_RANDOM_SUPPORT
+	random_t random_gen;
+#endif
+#if ENABLE_HUSH_JOB
+	int run_list_level;
+	unsigned last_jobid;
+	pid_t saved_tty_pgrp;
+	struct pipe *job_list;
+# define G_saved_tty_pgrp (G.saved_tty_pgrp)
+#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])
+#else
+# define G_x_mode 0
+#endif
+	char opt_s;
+	char opt_c;
+#if ENABLE_HUSH_INTERACTIVE
+	smallint promptmode; /* 0: PS1, 1: PS2 */
+#endif
+	smallint flag_SIGINT;
+#if ENABLE_HUSH_LOOPS
+	smallint flag_break_continue;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+	/* 0: outside of a function (or sourced file)
+	 * -1: inside of a function, ok to use return builtin
+	 * 1: return is invoked, skip all till end of func
+	 */
+	smallint flag_return_in_progress;
+# define G_flag_return_in_progress (G.flag_return_in_progress)
+#else
+# define G_flag_return_in_progress 0
+#endif
+	smallint exiting; /* used to prevent EXIT trap recursion */
+	/* These support $? */
+	smalluint last_exitcode;
+	smalluint expand_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;
+# define G_global_args_malloced (G.global_args_malloced)
+#else
+# define G_global_args_malloced 0
+#endif
+#if ENABLE_HUSH_BASH_COMPAT
+	int dead_job_exitcode; /* for "wait -n" */
+#endif
+	/* how many non-NULL argv's we have. NB: $# + 1 */
+	int global_argc;
+	char **global_argv;
+#if !BB_MMU
+	char *argv0_for_re_execing;
+#endif
+#if ENABLE_HUSH_LOOPS
+	unsigned depth_break_continue;
+	unsigned depth_of_loop;
+#endif
+#if ENABLE_HUSH_GETOPTS
+	unsigned getopt_count;
+#endif
+	const char *ifs;
+	char *ifs_whitespace; /* = G.ifs or malloced */
+	const char *cwd;
+	struct variable *top_var;
+	char **expanded_assignments;
+	struct variable **shadowed_vars_pp;
+	unsigned var_nest_level;
+#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_LOCAL
+	unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
+# endif
+	struct function *top_func;
+#endif
+	/* Signal and trap handling */
+#if ENABLE_HUSH_FAST
+	unsigned count_SIGCHLD;
+	unsigned handled_SIGCHLD;
+	smallint we_have_children;
+#endif
+#if ENABLE_HUSH_LINENO_VAR
+	unsigned parse_lineno;
+	unsigned execute_lineno;
+#endif
+	HFILE *HFILE_list;
+	HFILE *HFILE_stdin;
+	/* Which signals have non-DFL handler (even with no traps set)?
+	 * Set at the start to:
+	 * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
+	 * SPECIAL_INTERACTIVE_SIGS are cleared after fork.
+	 * The rest is cleared right before execv syscalls.
+	 * Other than these two times, never modified.
+	 */
+	unsigned special_sig_mask;
+#if ENABLE_HUSH_JOB
+	unsigned fatal_sig_mask;
+# define G_fatal_sig_mask (G.fatal_sig_mask)
+#else
+# define G_fatal_sig_mask 0
+#endif
+#if ENABLE_HUSH_TRAP
+	int pre_trap_exitcode;
+# if ENABLE_HUSH_FUNCTIONS
+	int return_exitcode;
+# endif
+	char **traps; /* char *traps[NSIG] */
+# define G_traps G.traps
+#else
+# define G_traps ((char**)NULL)
+#endif
+	sigset_t pending_set;
+#if ENABLE_HUSH_MEMLEAK
+	unsigned long memleak_value;
+#endif
+#if ENABLE_HUSH_MODE_X
+	unsigned x_mode_depth;
+	/* "set -x" output should not be redirectable with subsequent 2>FILE.
+	 * We dup fd#2 to x_mode_fd when "set -x" is executed, and use it
+	 * for all subsequent output.
+	 */
+	int x_mode_fd;
+	o_string x_mode_buf;
+#endif
+#if HUSH_DEBUG >= 2
+	int debug_indent;
+#endif
+	struct sigaction sa;
+	char optstring_buf[sizeof("eixcs")];
+#if BASH_EPOCH_VARS
+	char epoch_buf[sizeof("%llu.nnnnnn") + sizeof(long long)*3];
+#endif
+#if ENABLE_FEATURE_EDITING
+	char user_input_buf[CONFIG_FEATURE_EDITING_MAX_LEN];
+#endif
+};
+#define G (*ptr_to_globals)
+/* Not #defining name to G.name - this quickly gets unwieldy
+ * (too many defines). Also, I actually prefer to see when a variable
+ * is global, thus "G." prefix is a useful hint */
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	/* memset(&G.sa, 0, sizeof(G.sa)); */  \
+	sigfillset(&G.sa.sa_mask); \
+	G.sa.sa_flags = SA_RESTART; \
+} while (0)
+
+
+/* Function prototypes for builtins */
+static int builtin_cd(char **argv) FAST_FUNC;
+#if ENABLE_HUSH_ECHO
+static int builtin_echo(char **argv) FAST_FUNC;
+#endif
+static int builtin_eval(char **argv) FAST_FUNC;
+static int builtin_exec(char **argv) FAST_FUNC;
+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;
+#endif
+#if ENABLE_HUSH_GETOPTS
+static int builtin_getopts(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv) FAST_FUNC;
+#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_LOCAL
+static int builtin_local(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_MEMLEAK
+static int builtin_memleak(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_PRINTF
+static int builtin_printf(char **argv) FAST_FUNC;
+#endif
+static int builtin_pwd(char **argv) FAST_FUNC;
+#if ENABLE_HUSH_READ
+static int builtin_read(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_SET
+static int builtin_set(char **argv) FAST_FUNC;
+#endif
+static int builtin_shift(char **argv) FAST_FUNC;
+static int builtin_source(char **argv) FAST_FUNC;
+#if ENABLE_HUSH_TEST || BASH_TEST2
+static int builtin_test(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_TRAP
+static int builtin_trap(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_TYPE
+static int builtin_type(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_TIMES
+static int builtin_times(char **argv) FAST_FUNC;
+#endif
+static int builtin_true(char **argv) FAST_FUNC;
+#if ENABLE_HUSH_UMASK
+static int builtin_umask(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_UNSET
+static int builtin_unset(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_KILL
+static int builtin_kill(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_WAIT
+static int builtin_wait(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_LOOPS
+static int builtin_break(char **argv) FAST_FUNC;
+static int builtin_continue(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+static int builtin_return(char **argv) FAST_FUNC;
+#endif
+
+/* Table of built-in functions.  They can be forked or not, depending on
+ * context: within pipes, they fork.  As simple commands, they do not.
+ * When used in non-forking context, they can change global variables
+ * in the parent shell process.  If forked, of course they cannot.
+ * For example, 'unset foo | whatever' will parse and run, but foo will
+ * still be set at the end. */
+struct built_in_command {
+	const char *b_cmd;
+	int (*b_function)(char **argv) FAST_FUNC;
+#if ENABLE_HUSH_HELP
+	const char *b_descr;
+# define BLTIN(cmd, func, help) { cmd, func, help }
+#else
+# define BLTIN(cmd, func, help) { cmd, func }
+#endif
+};
+
+static const struct built_in_command bltins1[] ALIGN_PTR = {
+	BLTIN("."        , builtin_source  , "Run commands in file"),
+	BLTIN(":"        , builtin_true    , NULL),
+#if ENABLE_HUSH_JOB
+	BLTIN("bg"       , builtin_fg_bg   , "Resume job in background"),
+#endif
+#if ENABLE_HUSH_LOOPS
+	BLTIN("break"    , builtin_break   , "Exit loop"),
+#endif
+	BLTIN("cd"       , builtin_cd      , "Change directory"),
+#if ENABLE_HUSH_LOOPS
+	BLTIN("continue" , builtin_continue, "Start new loop iteration"),
+#endif
+	BLTIN("eval"     , builtin_eval    , "Construct and run shell command"),
+	BLTIN("exec"     , builtin_exec    , "Execute command, don't return to shell"),
+	BLTIN("exit"     , builtin_exit    , NULL),
+#if ENABLE_HUSH_EXPORT
+	BLTIN("export"   , builtin_export  , "Set environment variables"),
+#endif
+#if ENABLE_HUSH_JOB
+	BLTIN("fg"       , builtin_fg_bg   , "Bring job to foreground"),
+#endif
+#if ENABLE_HUSH_GETOPTS
+	BLTIN("getopts"  , builtin_getopts , NULL),
+#endif
+#if ENABLE_HUSH_HELP
+	BLTIN("help"     , builtin_help    , NULL),
+#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+	BLTIN("history"  , builtin_history , "Show history"),
+#endif
+#if ENABLE_HUSH_JOB
+	BLTIN("jobs"     , builtin_jobs    , "List jobs"),
+#endif
+#if ENABLE_HUSH_KILL
+	BLTIN("kill"     , builtin_kill    , "Send signals to processes"),
+#endif
+#if ENABLE_HUSH_LOCAL
+	BLTIN("local"    , builtin_local   , "Set local variables"),
+#endif
+#if ENABLE_HUSH_MEMLEAK
+	BLTIN("memleak"  , builtin_memleak , NULL),
+#endif
+#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
+#if ENABLE_HUSH_SET
+	BLTIN("set"      , builtin_set     , "Set positional parameters"),
+#endif
+	BLTIN("shift"    , builtin_shift   , "Shift positional parameters"),
+#if BASH_SOURCE
+	BLTIN("source"   , builtin_source  , NULL),
+#endif
+#if ENABLE_HUSH_TIMES
+	BLTIN("times"    , builtin_times   , NULL),
+#endif
+#if ENABLE_HUSH_TRAP
+	BLTIN("trap"     , builtin_trap    , "Trap signals"),
+#endif
+	BLTIN("true"     , builtin_true    , NULL),
+#if ENABLE_HUSH_TYPE
+	BLTIN("type"     , builtin_type    , "Show command type"),
+#endif
+#if ENABLE_HUSH_ULIMIT
+	BLTIN("ulimit"   , shell_builtin_ulimit, "Control resource limits"),
+#endif
+#if ENABLE_HUSH_UMASK
+	BLTIN("umask"    , builtin_umask   , "Set file creation mask"),
+#endif
+#if ENABLE_HUSH_UNSET
+	BLTIN("unset"    , builtin_unset   , "Unset variables"),
+#endif
+#if ENABLE_HUSH_WAIT
+	BLTIN("wait"     , builtin_wait    , "Wait for process to finish"),
+#endif
+};
+/* These builtins won't be used if we are on NOMMU and need to re-exec
+ * (it's cheaper to run an external program in this case):
+ */
+static const struct built_in_command bltins2[] ALIGN_PTR = {
+#if ENABLE_HUSH_TEST
+	BLTIN("["        , builtin_test    , NULL),
+#endif
+#if BASH_TEST2
+	BLTIN("[["       , builtin_test    , NULL),
+#endif
+#if ENABLE_HUSH_ECHO
+	BLTIN("echo"     , builtin_echo    , NULL),
+#endif
+#if ENABLE_HUSH_PRINTF
+	BLTIN("printf"   , builtin_printf  , NULL),
+#endif
+	BLTIN("pwd"      , builtin_pwd     , NULL),
+#if ENABLE_HUSH_TEST
+	BLTIN("test"     , builtin_test    , NULL),
+#endif
+};
+
+
+/* Debug printouts.
+ */
+#if HUSH_DEBUG >= 2
+/* prevent disasters with G.debug_indent < 0 */
+# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "")
+# define debug_enter() (G.debug_indent++)
+# define debug_leave() (G.debug_indent--)
+#else
+# define indent()      ((void)0)
+# define debug_enter() ((void)0)
+# define debug_leave() ((void)0)
+#endif
+
+#ifndef debug_printf
+# define debug_printf(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_parse
+# define debug_printf_parse(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_heredoc
+# define debug_printf_heredoc(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_exec
+#define debug_printf_exec(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_env
+# define debug_printf_env(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_jobs
+# define debug_printf_jobs(...) (indent(), fdprintf(2, __VA_ARGS__))
+# define DEBUG_JOBS 1
+#else
+# define DEBUG_JOBS 0
+#endif
+
+#ifndef debug_printf_expand
+# define debug_printf_expand(...) (indent(), fdprintf(2, __VA_ARGS__))
+# define DEBUG_EXPAND 1
+#else
+# define DEBUG_EXPAND 0
+#endif
+
+#ifndef debug_printf_varexp
+# define debug_printf_varexp(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_glob
+# define debug_printf_glob(...) (indent(), fdprintf(2, __VA_ARGS__))
+# define DEBUG_GLOB 1
+#else
+# 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
+
+#ifndef debug_printf_subst
+# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_prompt
+# define debug_printf_prompt(...) (indent(), fdprintf(2, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_clean
+# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__))
+# define DEBUG_CLEAN 1
+#else
+# define DEBUG_CLEAN 0
+#endif
+
+#if DEBUG_EXPAND
+static void debug_print_strings(const char *prefix, char **vv)
+{
+	indent();
+	fdprintf(2, "%s:\n", prefix);
+	while (*vv)
+		fdprintf(2, " '%s'\n", *vv++);
+}
+#else
+# define debug_print_strings(prefix, vv) ((void)0)
+#endif
+
+
+/* Leak hunting. Use hush_leaktool.sh for post-processing.
+ */
+#if LEAK_HUNTING
+static void *xxmalloc(int lineno, size_t size)
+{
+	void *ptr = xmalloc((size + 0xff) & ~0xff);
+	fdprintf(2, "line %d: malloc %p\n", lineno, ptr);
+	return ptr;
+}
+static void *xxrealloc(int lineno, void *ptr, size_t size)
+{
+	ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
+	fdprintf(2, "line %d: realloc %p\n", lineno, ptr);
+	return ptr;
+}
+static char *xxstrdup(int lineno, const char *str)
+{
+	char *ptr = xstrdup(str);
+	fdprintf(2, "line %d: strdup %p\n", lineno, ptr);
+	return ptr;
+}
+static void xxfree(void *ptr)
+{
+	fdprintf(2, "free %p\n", ptr);
+	free(ptr);
+}
+# define xmalloc(s)     xxmalloc(__LINE__, s)
+# define xrealloc(p, s) xxrealloc(__LINE__, p, s)
+# define xstrdup(s)     xxstrdup(__LINE__, s)
+# define free(p)        xxfree(p)
+#endif
+
+
+/* Syntax and runtime errors. They always abort scripts.
+ * In interactive use they usually discard unparsed and/or unexecuted commands
+ * and return to the prompt.
+ * HUSH_DEBUG >= 2 prints line number in this file where it was detected.
+ */
+#if HUSH_DEBUG < 2
+# define msg_and_die_if_script(lineno, ...)     msg_and_die_if_script(__VA_ARGS__)
+# define syntax_error(lineno, msg)              syntax_error(msg)
+# define syntax_error_at(lineno, msg)           syntax_error_at(msg)
+# define syntax_error_unterm_ch(lineno, ch)     syntax_error_unterm_ch(ch)
+# define syntax_error_unterm_str(lineno, s)     syntax_error_unterm_str(s)
+# define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch)
+#endif
+
+static void die_if_script(void)
+{
+	if (!G_interactive_fd) {
+		if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */
+			xfunc_error_retval = G.last_exitcode;
+		xfunc_die();
+	}
+}
+
+static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
+{
+	va_list p;
+
+#if HUSH_DEBUG >= 2
+	bb_error_msg("hush.c:%u", lineno);
+#endif
+	va_start(p, fmt);
+	bb_verror_msg(fmt, p, NULL);
+	va_end(p);
+	die_if_script();
+}
+
+static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
+{
+	if (msg)
+		bb_error_msg("syntax error: %s", msg);
+	else
+		bb_simple_error_msg("syntax error");
+	die_if_script();
+}
+
+static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
+{
+	bb_error_msg("syntax error at '%s'", msg);
+	die_if_script();
+}
+
+static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
+{
+	bb_error_msg("syntax error: unterminated %s", s);
+//? source4.tests fails: in bash, echo ${^} in script does not terminate the script
+//	die_if_script();
+}
+
+static void syntax_error_unterm_ch(unsigned lineno, char ch)
+{
+	char msg[2] = { ch, '\0' };
+	syntax_error_unterm_str(lineno, msg);
+}
+
+static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
+{
+	char msg[2];
+	msg[0] = ch;
+	msg[1] = '\0';
+#if HUSH_DEBUG >= 2
+	bb_error_msg("hush.c:%u", lineno);
+#endif
+	bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
+	die_if_script();
+}
+
+#if HUSH_DEBUG < 2
+# undef msg_and_die_if_script
+# undef syntax_error
+# undef syntax_error_at
+# undef syntax_error_unterm_ch
+# undef syntax_error_unterm_str
+# undef syntax_error_unexpected_ch
+#else
+# define msg_and_die_if_script(...)     msg_and_die_if_script(__LINE__, __VA_ARGS__)
+# define syntax_error(msg)              syntax_error(__LINE__, msg)
+# define syntax_error_at(msg)           syntax_error_at(__LINE__, msg)
+# define syntax_error_unterm_ch(ch)     syntax_error_unterm_ch(__LINE__, ch)
+# define syntax_error_unterm_str(s)     syntax_error_unterm_str(__LINE__, s)
+# define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch)
+#endif
+
+
+/* Utility functions
+ */
+/* Replace each \x with x in place, return ptr past NUL. */
+static char *unbackslash(char *src)
+{
+	char *dst = src = strchrnul(src, '\\');
+	while (1) {
+		if (*src == '\\') {
+			src++;
+			if (*src != '\0') {
+				/* \x -> x */
+				*dst++ = *src++;
+				continue;
+			}
+			/* else: "\<nul>". Do not delete this backslash.
+			 * Testcase: eval 'echo ok\'
+			 */
+			*dst++ = '\\';
+			/* fallthrough */
+		}
+		if ((*dst++ = *src++) == '\0')
+			break;
+	}
+	return dst;
+}
+
+static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
+{
+	int i;
+	unsigned count1;
+	unsigned count2;
+	char **v;
+
+	v = strings;
+	count1 = 0;
+	if (v) {
+		while (*v) {
+			count1++;
+			v++;
+		}
+	}
+	count2 = 0;
+	v = add;
+	while (*v) {
+		count2++;
+		v++;
+	}
+	v = xrealloc(strings, (count1 + count2 + 1) * sizeof(char*));
+	v[count1 + count2] = NULL;
+	i = count2;
+	while (--i >= 0)
+		v[count1 + i] = (need_to_dup ? xstrdup(add[i]) : add[i]);
+	return v;
+}
+#if LEAK_HUNTING
+static char **xx_add_strings_to_strings(int lineno, char **strings, char **add, int need_to_dup)
+{
+	char **ptr = add_strings_to_strings(strings, add, need_to_dup);
+	fdprintf(2, "line %d: add_strings_to_strings %p\n", lineno, ptr);
+	return ptr;
+}
+#define add_strings_to_strings(strings, add, need_to_dup) \
+	xx_add_strings_to_strings(__LINE__, strings, add, need_to_dup)
+#endif
+
+/* Note: takes ownership of "add" ptr (it is not strdup'ed) */
+static char **add_string_to_strings(char **strings, char *add)
+{
+	char *v[2];
+	v[0] = add;
+	v[1] = NULL;
+	return add_strings_to_strings(strings, v, /*dup:*/ 0);
+}
+#if LEAK_HUNTING
+static char **xx_add_string_to_strings(int lineno, char **strings, char *add)
+{
+	char **ptr = add_string_to_strings(strings, add);
+	fdprintf(2, "line %d: add_string_to_strings %p\n", lineno, ptr);
+	return ptr;
+}
+#define add_string_to_strings(strings, add) \
+	xx_add_string_to_strings(__LINE__, strings, add)
+#endif
+
+static void free_strings(char **strings)
+{
+	char **v;
+
+	if (!strings)
+		return;
+	v = strings;
+	while (*v) {
+		free(*v);
+		v++;
+	}
+	free(strings);
+}
+
+static int dup_CLOEXEC(int fd, int avoid_fd)
+{
+	int newfd;
+ repeat:
+	newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
+	if (newfd >= 0) {
+		if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+			fcntl(newfd, F_SETFD, FD_CLOEXEC);
+	} else { /* newfd < 0 */
+		if (errno == EBUSY)
+			goto repeat;
+		if (errno == EINTR)
+			goto repeat;
+	}
+	return newfd;
+}
+
+static int xdup_CLOEXEC_and_close(int fd, int avoid_fd)
+{
+	int newfd;
+ repeat:
+	newfd = fcntl(fd, F_DUPFD_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;
+		xfunc_die();
+	}
+	if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+		fcntl(newfd, F_SETFD, FD_CLOEXEC);
+	close(fd);
+	return newfd;
+}
+
+
+/* Manipulating HFILEs */
+static HFILE *hfopen(const char *name)
+{
+	HFILE *fp;
+	int fd;
+
+	fd = STDIN_FILENO;
+	if (name) {
+		fd = open(name, O_RDONLY | O_CLOEXEC);
+		if (fd < 0)
+			return NULL;
+		if (O_CLOEXEC == 0) /* ancient libc */
+			close_on_exec_on(fd);
+	}
+
+	fp = xmalloc(sizeof(*fp));
+	if (name == NULL)
+		G.HFILE_stdin = fp;
+	fp->fd = fd;
+	fp->cur = fp->end = fp->buf;
+	fp->next_hfile = G.HFILE_list;
+	G.HFILE_list = fp;
+	return fp;
+}
+static void hfclose(HFILE *fp)
+{
+	HFILE **pp = &G.HFILE_list;
+	while (*pp) {
+		HFILE *cur = *pp;
+		if (cur == fp) {
+			*pp = cur->next_hfile;
+			break;
+		}
+		pp = &cur->next_hfile;
+	}
+	if (fp->fd >= 0)
+		close(fp->fd);
+	free(fp);
+}
+static int refill_HFILE_and_getc(HFILE *fp)
+{
+	int n;
+
+	if (fp->fd < 0) {
+		/* Already saw EOF */
+		return EOF;
+	}
+#if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_EDITING
+	/* If user presses ^C, read() restarts after SIGINT (we use SA_RESTART).
+	 * IOW: ^C will not immediately stop line input.
+	 * But poll() is different: it does NOT restart after signals.
+	 */
+	if (fp == G.HFILE_stdin) {
+		struct pollfd pfd[1];
+		pfd[0].fd = fp->fd;
+		pfd[0].events = POLLIN;
+		n = poll(pfd, 1, -1);
+		if (n < 0
+		 /*&& errno == EINTR - assumed true */
+		 && sigismember(&G.pending_set, SIGINT)
+		) {
+			return '\0';
+		}
+	}
+#else
+/* if FEATURE_EDITING=y, we do not use this routine for interactive input */
+#endif
+	/* Try to buffer more input */
+	n = safe_read(fp->fd, fp->buf, sizeof(fp->buf));
+	if (n < 0) {
+		bb_simple_perror_msg("read error");
+		n = 0;
+	}
+	fp->cur = fp->buf;
+	fp->end = fp->buf + n;
+	if (n == 0) {
+		/* EOF/error */
+		close(fp->fd);
+		fp->fd = -1;
+		return EOF;
+	}
+	return (unsigned char)(*fp->cur++);
+}
+/* Inlined for common case of non-empty buffer.
+ */
+static ALWAYS_INLINE int hfgetc(HFILE *fp)
+{
+	if (fp->cur < fp->end)
+		return (unsigned char)(*fp->cur++);
+	/* Buffer empty */
+	return refill_HFILE_and_getc(fp);
+}
+static int move_HFILEs_on_redirect(int fd, int avoid_fd)
+{
+	HFILE *fl = G.HFILE_list;
+	while (fl) {
+		if (fd == fl->fd) {
+			/* We use it only on script files, they are all CLOEXEC */
+			fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
+			debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
+			return 1; /* "found and moved" */
+		}
+		fl = fl->next_hfile;
+	}
+#if ENABLE_HUSH_MODE_X
+	if (G.x_mode_fd > 0 && fd == G.x_mode_fd) {
+		G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
+		return 1; /* "found and moved" */
+	}
+#endif
+	return 0; /* "not in the list" */
+}
+#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
+static void close_all_HFILE_list(void)
+{
+	HFILE *fl = G.HFILE_list;
+	while (fl) {
+		/* hfclose would also free HFILE object.
+		 * It is disastrous if we share memory with a vforked parent.
+		 * I'm not sure we never come here after vfork.
+		 * Therefore just close fd, nothing more.
+		 *
+		 * ">" instead of ">=": we don't close fd#0,
+		 * interactive shell uses hfopen(NULL) as stdin input
+		 * which has fl->fd == 0, but fd#0 gets redirected in pipes.
+		 * If we'd close it here, then e.g. interactive "set | sort"
+		 * with NOFORKed sort, would have sort's input fd closed.
+		 */
+		if (fl->fd > 0)
+			/*hfclose(fl); - unsafe */
+			close(fl->fd);
+		fl = fl->next_hfile;
+	}
+}
+#endif
+static int fd_in_HFILEs(int fd)
+{
+	HFILE *fl = G.HFILE_list;
+	while (fl) {
+		if (fl->fd == fd)
+			return 1;
+		fl = fl->next_hfile;
+	}
+	return 0;
+}
+
+
+/* Helpers for setting new $n and restoring them back
+ */
+typedef struct save_arg_t {
+	char *sv_argv0;
+	char **sv_g_argv;
+	int sv_g_argc;
+	IF_HUSH_SET(smallint sv_g_malloced;)
+} save_arg_t;
+
+static void save_and_replace_G_args(save_arg_t *sv, char **argv)
+{
+	sv->sv_argv0 = argv[0];
+	sv->sv_g_argv = G.global_argv;
+	sv->sv_g_argc = G.global_argc;
+	IF_HUSH_SET(sv->sv_g_malloced = G.global_args_malloced;)
+
+	argv[0] = G.global_argv[0]; /* retain $0 */
+	G.global_argv = argv;
+	IF_HUSH_SET(G.global_args_malloced = 0;)
+
+	G.global_argc = 1 + string_array_len(argv + 1);
+}
+
+static void restore_G_args(save_arg_t *sv, char **argv)
+{
+#if ENABLE_HUSH_SET
+	if (G.global_args_malloced) {
+		/* someone ran "set -- arg1 arg2 ...", undo */
+		char **pp = G.global_argv;
+		while (*++pp) /* note: does not free $0 */
+			free(*pp);
+		free(G.global_argv);
+	}
+#endif
+	argv[0] = sv->sv_argv0;
+	G.global_argv = sv->sv_g_argv;
+	G.global_argc = sv->sv_g_argc;
+	IF_HUSH_SET(G.global_args_malloced = sv->sv_g_malloced;)
+}
+
+
+/* Basic theory of signal handling in shell
+ * ========================================
+ * This does not describe what hush does, rather, it is current understanding
+ * what it _should_ do. If it doesn't, it's a bug.
+ * http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#trap
+ *
+ * Signals are handled only after each pipe ("cmd | cmd | cmd" thing)
+ * is finished or backgrounded. It is the same in interactive and
+ * non-interactive shells, and is the same regardless of whether
+ * a user trap handler is installed or a shell special one is in effect.
+ * ^C or ^Z from keyboard seems to execute "at once" because it usually
+ * backgrounds (i.e. stops) or kills all members of currently running
+ * pipe.
+ *
+ * Wait builtin is interruptible by signals for which user trap is set
+ * or by SIGINT in interactive shell.
+ *
+ * Trap handlers will execute even within trap handlers. (right?)
+ *
+ * User trap handlers are forgotten when subshell ("(cmd)") is entered,
+ * except for handlers set to '' (empty string).
+ *
+ * If job control is off, backgrounded commands ("cmd &")
+ * have SIGINT, SIGQUIT set to SIG_IGN.
+ *
+ * Commands which are run in command substitution ("`cmd`")
+ * have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN.
+ *
+ * Ordinary commands have signals set to SIG_IGN/DFL as inherited
+ * by the shell from its parent.
+ *
+ * Signals which differ from SIG_DFL action
+ * (note: child (i.e., [v]forked) shell is not an interactive shell):
+ *
+ * SIGQUIT: ignore
+ * SIGTERM (interactive): ignore
+ * SIGHUP (interactive):
+ *    send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit
+ * SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore
+ *    Note that ^Z is handled not by trapping SIGTSTP, but by seeing
+ *    that all pipe members are stopped. Try this in bash:
+ *    while :; do :; done - ^Z does not background it
+ *    (while :; do :; done) - ^Z backgrounds it
+ * SIGINT (interactive): wait for last pipe, ignore the rest
+ *    of the command line, show prompt. NB: ^C does not send SIGINT
+ *    to interactive shell while shell is waiting for a pipe,
+ *    since shell is bg'ed (is not in foreground process group).
+ *    Example 1: this waits 5 sec, but does not execute ls:
+ *    "echo $$; sleep 5; ls -l" + "kill -INT <pid>"
+ *    Example 2: this does not wait and does not execute ls:
+ *    "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>"
+ *    Example 3: this does not wait 5 sec, but executes ls:
+ *    "sleep 5; ls -l" + press ^C
+ *    Example 4: this does not wait and does not execute ls:
+ *    "sleep 5 & wait; ls -l" + press ^C
+ *
+ * (What happens to signals which are IGN on shell start?)
+ * (What happens with signal mask on shell start?)
+ *
+ * Old implementation
+ * ==================
+ * We use in-kernel pending signal mask to determine which signals were sent.
+ * We block all signals which we don't want to take action immediately,
+ * i.e. we block all signals which need to have special handling as described
+ * above, and all signals which have traps set.
+ * After each pipe execution, we extract any pending signals via sigtimedwait()
+ * and act on them.
+ *
+ * unsigned special_sig_mask: a mask of such "special" signals
+ * sigset_t blocked_set:  current blocked signal set
+ *
+ * "trap - SIGxxx":
+ *    clear bit in blocked_set unless it is also in special_sig_mask
+ * "trap 'cmd' SIGxxx":
+ *    set bit in blocked_set (even if 'cmd' is '')
+ * after [v]fork, if we plan to be a shell:
+ *    unblock signals with special interactive handling
+ *    (child shell is not interactive),
+ *    unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ *    POSIX says fork clears pending signal mask in child - no need to clear it.
+ *    Restore blocked signal set to one inherited by shell just prior to exec.
+ *
+ * Note: as a result, we do not use signal handlers much. The only uses
+ * are to count SIGCHLDs
+ * and to restore tty pgrp on signal-induced exit.
+ *
+ * Note 2 (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to '' (ignore) are NOT reset to defaults. We do the same.
+ *
+ * Problem: the above approach makes it unwieldy to catch signals while
+ * we are in read builtin, or while we read commands from stdin:
+ * masked signals are not visible!
+ *
+ * New implementation
+ * ==================
+ * We record each signal we are interested in by installing signal handler
+ * for them - a bit like emulating kernel pending signal mask in userspace.
+ * We are interested in: signals which need to have special handling
+ * as described above, and all signals which have traps set.
+ * Signals are recorded in pending_set.
+ * After each pipe execution, we extract any pending signals
+ * and act on them.
+ *
+ * unsigned special_sig_mask: a mask of shell-special signals.
+ * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp.
+ * char *traps[sig] if trap for sig is set (even if it's '').
+ * sigset_t pending_set: set of sigs we received.
+ *
+ * "trap - SIGxxx":
+ *    if sig is in special_sig_mask, set handler back to:
+ *        record_pending_signo, or to IGN if it's a tty stop signal
+ *    if sig is in fatal_sig_mask, set handler back to sigexit.
+ *    else: set handler back to SIG_DFL
+ * "trap 'cmd' SIGxxx":
+ *    set handler to record_pending_signo.
+ * "trap '' SIGxxx":
+ *    set handler to SIG_IGN.
+ * after [v]fork, if we plan to be a shell:
+ *    set signals with special interactive handling to SIG_DFL
+ *    (because child shell is not interactive),
+ *    unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ *    POSIX says fork clears pending signal mask in child - no need to clear it.
+ *
+ * To make wait builtin interruptible, we handle SIGCHLD as special signal,
+ * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it.
+ *
+ * Note (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to '' (ignore) are NOT reset to defaults. We do the same.
+ */
+enum {
+	SPECIAL_INTERACTIVE_SIGS = 0
+		| (1 << SIGTERM)
+		| (1 << SIGINT)
+		| (1 << SIGHUP)
+		,
+	SPECIAL_JOBSTOP_SIGS = 0
+#if ENABLE_HUSH_JOB
+		| (1 << SIGTTIN)
+		| (1 << SIGTTOU)
+		| (1 << SIGTSTP)
+#endif
+		,
+};
+
+static void record_pending_signo(int sig)
+{
+	sigaddset(&G.pending_set, sig);
+#if ENABLE_HUSH_FAST
+	if (sig == SIGCHLD) {
+		G.count_SIGCHLD++;
+//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+	}
+#endif
+}
+
+static sighandler_t install_sighandler(int sig, sighandler_t handler)
+{
+	struct sigaction old_sa;
+
+	/* We could use signal() to install handlers... almost:
+	 * except that we need to mask ALL signals while handlers run.
+	 * I saw signal nesting in strace, race window isn't small.
+	 * SA_RESTART is also needed, but in Linux, signal()
+	 * sets SA_RESTART too.
+	 */
+	/* memset(&G.sa, 0, sizeof(G.sa)); - already done */
+	/* sigfillset(&G.sa.sa_mask);      - already done */
+	/* G.sa.sa_flags = SA_RESTART;     - already done */
+	G.sa.sa_handler = handler;
+	sigaction(sig, &G.sa, &old_sa);
+	return old_sa.sa_handler;
+}
+
+static void hush_exit(int exitcode) NORETURN;
+
+static void restore_ttypgrp_and__exit(void) NORETURN;
+static void restore_ttypgrp_and__exit(void)
+{
+	/* xfunc has failed! die die die */
+	/* no EXIT traps, this is an escape hatch! */
+	G.exiting = 1;
+	hush_exit(xfunc_error_retval);
+}
+
+#if ENABLE_HUSH_JOB
+
+/* Needed only on some libc:
+ * It was observed that on exit(), fgetc'ed buffered data
+ * gets "unwound" via lseek(fd, -NUM, SEEK_CUR).
+ * With the net effect that even after fork(), not vfork(),
+ * exit() in NOEXECed applet in "sh SCRIPT":
+ *	noexec_applet_here
+ *	echo END_OF_SCRIPT
+ * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT".
+ * This makes "echo END_OF_SCRIPT" executed twice.
+ * Similar problems can be seen with msg_and_die_if_script() -> xfunc_die()
+ * and in `cmd` handling.
+ * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit():
+ */
+static void fflush_and__exit(void) NORETURN;
+static void fflush_and__exit(void)
+{
+	fflush_all();
+	_exit(xfunc_error_retval);
+}
+
+/* After [v]fork, in child: do not restore tty pgrp on xfunc death */
+# define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit)
+/* After [v]fork, in parent: restore tty pgrp on xfunc death */
+# define enable_restore_tty_pgrp_on_exit()  (die_func = restore_ttypgrp_and__exit)
+
+/* Restores tty foreground process group, and exits.
+ * May be called as signal handler for fatal signal
+ * (will resend signal to itself, producing correct exit state)
+ * or called directly with -EXITCODE.
+ * We also call it if xfunc is exiting.
+ */
+static void sigexit(int sig) NORETURN;
+static void sigexit(int sig)
+{
+	/* Careful: we can end up here after [v]fork. Do not restore
+	 * tty pgrp then, only top-level shell process does that */
+	if (G_saved_tty_pgrp && getpid() == G.root_pid) {
+		/* Disable all signals: job control, SIGPIPE, etc.
+		 * Mostly paranoid measure, to prevent infinite SIGTTOU.
+		 */
+		sigprocmask_allsigs(SIG_BLOCK);
+		tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+	}
+
+	/* Not a signal, just exit */
+	if (sig <= 0)
+		_exit(- sig);
+
+	kill_myself_with_sig(sig); /* does not return */
+}
+#else
+
+# define disable_restore_tty_pgrp_on_exit() ((void)0)
+# define enable_restore_tty_pgrp_on_exit()  ((void)0)
+
+#endif
+
+static sighandler_t pick_sighandler(unsigned sig)
+{
+	sighandler_t handler = SIG_DFL;
+	if (sig < sizeof(unsigned)*8) {
+		unsigned sigmask = (1 << sig);
+
+#if ENABLE_HUSH_JOB
+		/* is sig fatal? */
+		if (G_fatal_sig_mask & sigmask)
+			handler = sigexit;
+		else
+#endif
+		/* sig has special handling? */
+		if (G.special_sig_mask & sigmask) {
+			handler = record_pending_signo;
+			/* TTIN/TTOU/TSTP can't be set to record_pending_signo
+			 * in order to ignore them: they will be raised
+			 * in an endless loop when we try to do some
+			 * terminal ioctls! We do have to _ignore_ these.
+			 */
+			if (SPECIAL_JOBSTOP_SIGS & sigmask)
+				handler = SIG_IGN;
+		}
+	}
+	return handler;
+}
+
+/* Restores tty foreground process group, and exits. */
+static void hush_exit(int exitcode)
+{
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+	save_history(G.line_input_state); /* may be NULL */
+#endif
+
+	fflush_all();
+	if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) {
+		char *argv[3];
+		/* argv[0] is unused */
+		argv[1] = xstrdup(G_traps[0]); /* copy, since EXIT trap handler may modify G_traps[0] */
+		argv[2] = NULL;
+		G.exiting = 1; /* prevent EXIT trap recursion */
+		/* Note: G_traps[0] is not cleared!
+		 * "trap" will still show it, if executed
+		 * in the handler */
+		builtin_eval(argv);
+	}
+
+#if ENABLE_FEATURE_CLEAN_UP
+	{
+		struct variable *cur_var;
+		if (G.cwd != bb_msg_unknown)
+			free((char*)G.cwd);
+		cur_var = G.top_var;
+		while (cur_var) {
+			struct variable *tmp = cur_var;
+			if (!cur_var->max_len)
+				free(cur_var->varstr);
+			cur_var = cur_var->next;
+			free(tmp);
+		}
+	}
+#endif
+
+	fflush_all();
+#if ENABLE_HUSH_JOB
+	sigexit(- (exitcode & 0xff));
+#else
+	_exit(exitcode);
+#endif
+}
+
+//TODO: return a mask of ALL handled sigs?
+static int check_and_run_traps(void)
+{
+	int last_sig = 0;
+
+	while (1) {
+		int sig;
+
+		if (sigisemptyset(&G.pending_set))
+			break;
+		sig = 0;
+		do {
+			sig++;
+			if (sigismember(&G.pending_set, sig)) {
+				sigdelset(&G.pending_set, sig);
+				goto got_sig;
+			}
+		} while (sig < NSIG);
+		break;
+ got_sig:
+#if ENABLE_HUSH_TRAP
+		if (G_traps && G_traps[sig]) {
+			debug_printf_exec("%s: sig:%d handler:'%s'\n", __func__, sig, G.traps[sig]);
+			if (G_traps[sig][0]) {
+				/* We have user-defined handler */
+				smalluint save_rcode;
+				int save_pre;
+				char *argv[3];
+				/* argv[0] is unused */
+				argv[1] = xstrdup(G_traps[sig]);
+				/* why strdup? trap can modify itself: trap 'trap "echo oops" INT' INT */
+				argv[2] = NULL;
+				save_pre = G.pre_trap_exitcode;
+				G.pre_trap_exitcode = save_rcode = G.last_exitcode;
+				builtin_eval(argv);
+				free(argv[1]);
+				G.pre_trap_exitcode = save_pre;
+				G.last_exitcode = save_rcode;
+# if ENABLE_HUSH_FUNCTIONS
+				if (G.return_exitcode >= 0) {
+					debug_printf_exec("trap exitcode:%d\n", G.return_exitcode);
+					G.last_exitcode = G.return_exitcode;
+				}
+# endif
+				last_sig = sig;
+			} /* else: "" trap, ignoring signal */
+			continue;
+		}
+#endif
+		/* not a trap: special action */
+		switch (sig) {
+		case SIGINT:
+			debug_printf_exec("%s: sig:%d default SIGINT handler\n", __func__, sig);
+			G.flag_SIGINT = 1;
+			last_sig = sig;
+			break;
+#if ENABLE_HUSH_JOB
+		case SIGHUP: {
+//TODO: why are we doing this? ash and dash don't do this,
+//they have no handler for SIGHUP at all,
+//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
+			struct pipe *job;
+			debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
+			/* bash is observed to signal whole process groups,
+			 * not individual processes */
+			for (job = G.job_list; job; job = job->next) {
+				if (job->pgrp <= 0)
+					continue;
+				debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+				if (kill(- job->pgrp, SIGHUP) == 0)
+					kill(- job->pgrp, SIGCONT);
+			}
+			sigexit(SIGHUP);
+		}
+#endif
+#if ENABLE_HUSH_FAST
+		case SIGCHLD:
+			debug_printf_exec("%s: sig:%d default SIGCHLD handler\n", __func__, sig);
+			G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+			/* Note:
+			 * We don't do 'last_sig = sig' here -> NOT returning this sig.
+			 * This simplifies wait builtin a bit.
+			 */
+			break;
+#endif
+		default: /* ignored: */
+			debug_printf_exec("%s: sig:%d default handling is to ignore\n", __func__, sig);
+			/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+			/* Note:
+			 * We don't do 'last_sig = sig' here -> NOT returning this sig.
+			 * Example: wait is not interrupted by TERM
+			 * in interactive shell, because TERM is ignored.
+			 */
+			break;
+		}
+	}
+	return last_sig;
+}
+
+
+static const char *get_cwd(int force)
+{
+	if (force || G.cwd == NULL) {
+		/* xrealloc_getcwd_or_warn(arg) calls free(arg),
+		 * we must not try to free(bb_msg_unknown) */
+		if (G.cwd == bb_msg_unknown)
+			G.cwd = NULL;
+		G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
+		if (!G.cwd)
+			G.cwd = bb_msg_unknown;
+	}
+	return G.cwd;
+}
+
+
+/*
+ * Shell and environment variable support
+ */
+static struct variable **get_ptr_to_local_var(const char *name, unsigned len)
+{
+	struct variable **pp;
+	struct variable *cur;
+
+	pp = &G.top_var;
+	while ((cur = *pp) != NULL) {
+		if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
+			return pp;
+		pp = &cur->next;
+	}
+	return NULL;
+}
+
+static const char* FAST_FUNC get_local_var_value(const char *name)
+{
+	struct variable **vpp;
+	unsigned len = strlen(name);
+
+	if (G.expanded_assignments) {
+		char **cpp = G.expanded_assignments;
+		while (*cpp) {
+			char *cp = *cpp;
+			if (strncmp(cp, name, len) == 0 && cp[len] == '=')
+				return cp + len + 1;
+			cpp++;
+		}
+	}
+
+	vpp = get_ptr_to_local_var(name, len);
+	if (vpp)
+		return (*vpp)->varstr + len + 1;
+
+	if (strcmp(name, "PPID") == 0)
+		return utoa(G.root_ppid);
+	// bash compat: UID? EUID?
+#if ENABLE_HUSH_RANDOM_SUPPORT
+	if (strcmp(name, "RANDOM") == 0)
+		return utoa(next_random(&G.random_gen));
+#endif
+#if ENABLE_HUSH_LINENO_VAR
+	if (strcmp(name, "LINENO") == 0)
+		return utoa(G.execute_lineno);
+#endif
+#if BASH_EPOCH_VARS
+	{
+		const char *fmt = NULL;
+		if (strcmp(name, "EPOCHSECONDS") == 0)
+			fmt = "%llu";
+		else if (strcmp(name, "EPOCHREALTIME") == 0)
+			fmt = "%llu.%06u";
+		if (fmt) {
+			struct timeval tv;
+			xgettimeofday(&tv);
+			sprintf(G.epoch_buf, fmt, (unsigned long long)tv.tv_sec,
+					(unsigned)tv.tv_usec);
+			return G.epoch_buf;
+		}
+	}
+#endif
+	return NULL;
+}
+
+#if ENABLE_HUSH_GETOPTS
+static void handle_changed_special_names(const char *name, unsigned name_len)
+{
+	if (name_len == 6) {
+		if (strncmp(name, "OPTIND", 6) == 0) {
+			G.getopt_count = 0;
+			return;
+		}
+	}
+}
+#else
+/* Do not even bother evaluating arguments */
+# define handle_changed_special_names(...) ((void)0)
+#endif
+
+/* str holds "NAME=VAL" and is expected to be malloced.
+ * We take ownership of it.
+ */
+#define SETFLAG_EXPORT   (1 << 0)
+#define SETFLAG_UNEXPORT (1 << 1)
+#define SETFLAG_MAKE_RO  (1 << 2)
+#define SETFLAG_VARLVL_SHIFT   3
+static int set_local_var(char *str, unsigned flags)
+{
+	struct variable **cur_pp;
+	struct variable *cur;
+	char *free_me = NULL;
+	char *eq_sign;
+	int name_len;
+	int retval;
+	unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
+
+	eq_sign = strchr(str, '=');
+	if (HUSH_DEBUG && !eq_sign)
+		bb_simple_error_msg_and_die("BUG in setvar");
+
+	name_len = eq_sign - str + 1; /* including '=' */
+	cur_pp = &G.top_var;
+	while ((cur = *cur_pp) != NULL) {
+		if (strncmp(cur->varstr, str, name_len) != 0) {
+			cur_pp = &cur->next;
+			continue;
+		}
+
+		/* We found an existing var with this name */
+		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 ?
+			debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
+			*eq_sign = '\0';
+			unsetenv(str);
+			*eq_sign = '=';
+		}
+		if (cur->var_nest_level < local_lvl) {
+			/* bash 3.2.33(1) and exported vars:
+			 * # export z=z
+			 * # f() { local z=a; env | grep ^z; }
+			 * # f
+			 * z=a
+			 * # env | grep ^z
+			 * z=z
+			 */
+			if (cur->flg_export)
+				flags |= SETFLAG_EXPORT;
+			/* New variable is local ("local VAR=VAL" or
+			 * "VAR=VAL cmd")
+			 * and existing one is global, or local
+			 * on a lower level that new one.
+			 * Remove it from global variable list:
+			 */
+			*cur_pp = cur->next;
+			if (G.shadowed_vars_pp) {
+				/* Save in "shadowed" list */
+				debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n",
+					cur->flg_export ? "exported " : "",
+					cur->varstr, cur->var_nest_level, str, local_lvl
+				);
+				cur->next = *G.shadowed_vars_pp;
+				*G.shadowed_vars_pp = cur;
+			} else {
+				/* Came from pseudo_exec_argv(), no need to save: delete it */
+				debug_printf_env("shadow-deleting %s'%s'/%u by '%s'/%u\n",
+					cur->flg_export ? "exported " : "",
+					cur->varstr, cur->var_nest_level, str, local_lvl
+				);
+				if (cur->max_len == 0) /* allocated "VAR=VAL"? */
+					free_me = cur->varstr; /* then free it later */
+				free(cur);
+			}
+			break;
+		}
+
+		if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
+			debug_printf_env("assignement '%s' does not change anything\n", str);
+ free_and_exp:
+			free(str);
+			goto exp;
+		}
+
+		/* Replace the value in the found "struct variable" */
+		if (cur->max_len != 0) {
+			if (cur->max_len >= strnlen(str, cur->max_len + 1)) {
+				/* This one is from startup env, reuse space */
+				debug_printf_env("reusing startup env for '%s'\n", str);
+				strcpy(cur->varstr, str);
+				goto free_and_exp;
+			}
+			/* Can't reuse */
+			cur->max_len = 0;
+			goto set_str_and_exp;
+		}
+		/* max_len == 0 signifies "malloced" var, which we can
+		 * (and have to) free. But we can't free(cur->varstr) here:
+		 * if cur->flg_export is 1, it is in the environment.
+		 * We should either unsetenv+free, or wait until putenv,
+		 * then putenv(new)+free(old).
+		 */
+		free_me = cur->varstr;
+		goto set_str_and_exp;
+	}
+
+	/* Not found or shadowed - create new variable struct */
+	debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl);
+	cur = xzalloc(sizeof(*cur));
+	cur->var_nest_level = local_lvl;
+	cur->next = *cur_pp;
+	*cur_pp = cur;
+
+ set_str_and_exp:
+	cur->varstr = str;
+ exp:
+#if !BB_MMU || ENABLE_HUSH_READONLY
+	if (flags & SETFLAG_MAKE_RO) {
+		cur->flg_read_only = 1;
+	}
+#endif
+	if (flags & SETFLAG_EXPORT)
+		cur->flg_export = 1;
+	retval = 0;
+	if (cur->flg_export) {
+		if (flags & SETFLAG_UNEXPORT) {
+			cur->flg_export = 0;
+			/* unsetenv was already done */
+		} else {
+			debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level);
+			retval = putenv(cur->varstr);
+			/* fall through to "free(free_me)" -
+			 * only now we can free old exported malloced string
+			 */
+		}
+	}
+	free(free_me);
+
+	handle_changed_special_names(cur->varstr, name_len - 1);
+
+	return retval;
+}
+
+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, /*flag:*/ 0);
+}
+
+/* Used at startup and after each cd */
+static void set_pwd_var(unsigned flag)
+{
+	set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag);
+}
+
+#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
+static int unset_local_var_len(const char *name, int name_len)
+{
+	struct variable *cur;
+	struct variable **cur_pp;
+
+	cur_pp = &G.top_var;
+	while ((cur = *cur_pp) != NULL) {
+		if (strncmp(cur->varstr, name, name_len) == 0
+		 && cur->varstr[name_len] == '='
+		) {
+			if (cur->flg_read_only) {
+				bb_error_msg("%s: readonly variable", name);
+				return EXIT_FAILURE;
+			}
+
+			*cur_pp = cur->next;
+			debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
+			bb_unsetenv(cur->varstr);
+			if (!cur->max_len)
+				free(cur->varstr);
+			free(cur);
+
+			break;
+		}
+		cur_pp = &cur->next;
+	}
+
+	/* Handle "unset LINENO" et al even if did not find the variable to unset */
+	handle_changed_special_names(name, name_len);
+
+	return EXIT_SUCCESS;
+}
+
+static int unset_local_var(const char *name)
+{
+	return unset_local_var_len(name, strlen(name));
+}
+#endif
+
+
+/*
+ * Helpers for "var1=val1 var2=val2 cmd" feature
+ */
+static void add_vars(struct variable *var)
+{
+	struct variable *next;
+
+	while (var) {
+		next = var->next;
+		var->next = G.top_var;
+		G.top_var = var;
+		if (var->flg_export) {
+			debug_printf_env("%s: restoring exported '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
+			putenv(var->varstr);
+		} else {
+			debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
+		}
+		var = next;
+	}
+}
+
+/* We put strings[i] into variable table and possibly putenv them.
+ * If variable is read only, we can free the strings[i]
+ * which attempts to overwrite it.
+ * The strings[] vector itself is freed.
+ */
+static void set_vars_and_save_old(char **strings)
+{
+	char **s;
+
+	if (!strings)
+		return;
+
+	s = strings;
+	while (*s) {
+		struct variable *var_p;
+		struct variable **var_pp;
+		char *eq;
+
+		eq = strchr(*s, '=');
+		if (HUSH_DEBUG && !eq)
+			bb_simple_error_msg_and_die("BUG in varexp4");
+		var_pp = get_ptr_to_local_var(*s, eq - *s);
+		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;
+			}
+			/* below, set_local_var() with nest level will
+			 * "shadow" (remove) this variable from
+			 * global linked list.
+			 */
+		}
+		debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level);
+		set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT);
+		s++;
+ next: ;
+	}
+	free(strings);
+}
+
+
+/*
+ * Unicode helper
+ */
+static void reinit_unicode_for_hush(void)
+{
+	/* Unicode support should be activated even if LANG is set
+	 * _during_ shell execution, not only if it was set when
+	 * shell was started. Therefore, re-check LANG every time:
+	 */
+	if (ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
+	 || ENABLE_UNICODE_USING_LOCALE
+	) {
+		const char *s = get_local_var_value("LC_ALL");
+		if (!s) s = get_local_var_value("LC_CTYPE");
+		if (!s) s = get_local_var_value("LANG");
+		reinit_unicode(s);
+	}
+}
+
+/*
+ * in_str support (strings, and "strings" read from files).
+ */
+
+#if ENABLE_HUSH_INTERACTIVE
+/* To test correct lineedit/interactive behavior, type from command line:
+ *	echo $P\
+ *	\
+ *	AT\
+ *	H\
+ *	\
+ * It exercises a lot of corner cases.
+ */
+static const char *setup_prompt_string(void)
+{
+	const char *prompt_str;
+
+	debug_printf_prompt("%s promptmode:%d\n", __func__, G.promptmode);
+
+# if ENABLE_FEATURE_EDITING_FANCY_PROMPT
+	prompt_str = get_local_var_value(G.promptmode == 0 ? "PS1" : "PS2");
+	if (!prompt_str)
+		prompt_str = "";
+# else
+	prompt_str = "> "; /* if PS2, else... */
+	if (G.promptmode == 0) { /* PS1 */
+		/* No fancy prompts supported, (re)generate "CURDIR $ " by hand */
+		free(G.PS1);
+		/* bash uses $PWD value, even if it is set by user.
+		 * It uses current dir only if PWD is unset.
+		 * We always use current dir. */
+		prompt_str = G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
+	}
+# endif
+	debug_printf("prompt_str '%s'\n", prompt_str);
+	return prompt_str;
+}
+static int get_user_input(struct in_str *i)
+{
+# if ENABLE_FEATURE_EDITING
+	/* In EDITING case, this function reads next input line,
+	 * saves it in i->p, then returns 1st char of it.
+	 */
+	int r;
+	const char *prompt_str;
+
+	prompt_str = setup_prompt_string();
+	for (;;) {
+		reinit_unicode_for_hush();
+		G.flag_SIGINT = 0;
+		/* buglet: SIGINT will not make new prompt to appear _at once_,
+		 * only after <Enter>. (^C works immediately) */
+		r = read_line_input(G.line_input_state, prompt_str,
+				G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
+		);
+		/* read_line_input intercepts ^C, "convert" it to SIGINT */
+		if (r == 0) {
+			raise(SIGINT);
+		}
+		check_and_run_traps();
+		if (r != 0 && !G.flag_SIGINT)
+			break;
+		/* ^C or SIGINT: repeat */
+		/* bash prints ^C even on real SIGINT (non-kbd generated) */
+		write(STDOUT_FILENO, "^C\n", 3);
+		G.last_exitcode = 128 | SIGINT;
+	}
+	if (r < 0) {
+		/* EOF/error detected */
+		/* ^D on interactive input goes to next line before exiting: */
+		write(STDOUT_FILENO, "\n", 1);
+		i->p = NULL;
+		i->peek_buf[0] = r = EOF;
+		return r;
+	}
+	i->p = G.user_input_buf;
+	return (unsigned char)*i->p++;
+# else
+	/* In !EDITING case, this function gets called for every char.
+	 * Buffering happens deeper in the call chain, in hfgetc(i->file).
+	 */
+	int r;
+
+	for (;;) {
+		G.flag_SIGINT = 0;
+		if (i->last_char == '\0' || i->last_char == '\n') {
+			const char *prompt_str = setup_prompt_string();
+			/* Why check_and_run_traps here? Try this interactively:
+			 * $ trap 'echo INT' INT; (sleep 2; kill -INT $$) &
+			 * $ <[enter], repeatedly...>
+			 * Without check_and_run_traps, handler never runs.
+			 */
+			check_and_run_traps();
+			fputs_stdout(prompt_str);
+			fflush_all();
+		}
+		r = hfgetc(i->file);
+		/* In !ENABLE_FEATURE_EDITING we don't use read_line_input,
+		 * no ^C masking happens during fgetc, no special code for ^C:
+		 * it generates SIGINT as usual.
+		 */
+		check_and_run_traps();
+		if (r != '\0' && !G.flag_SIGINT)
+			break;
+		if (G.flag_SIGINT) {
+			/* ^C or SIGINT: repeat */
+			/* bash prints ^C even on real SIGINT (non-kbd generated) */
+			/* kernel prints "^C" itself, just print newline: */
+			write(STDOUT_FILENO, "\n", 1);
+			G.last_exitcode = 128 | SIGINT;
+		}
+	}
+	return r;
+# endif
+}
+/* This is the magic location that prints prompts
+ * and gets data back from the user */
+static int fgetc_interactive(struct in_str *i)
+{
+	int ch;
+	/* If it's interactive stdin, get new line. */
+	if (G_interactive_fd && i->file == G.HFILE_stdin) {
+		/* Returns first char (or EOF), the rest is in i->p[] */
+		ch = get_user_input(i);
+		G.promptmode = 1; /* PS2 */
+		debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
+	} else {
+		/* Not stdin: script file, sourced file, etc */
+		do ch = hfgetc(i->file); while (ch == '\0');
+	}
+	return ch;
+}
+#else  /* !INTERACTIVE */
+static ALWAYS_INLINE int fgetc_interactive(struct in_str *i)
+{
+	int ch;
+	do ch = hfgetc(i->file); while (ch == '\0');
+	return ch;
+}
+#endif  /* !INTERACTIVE */
+
+static int i_getch(struct in_str *i)
+{
+	int ch;
+
+	if (!i->file) {
+		/* string-based in_str */
+		ch = (unsigned char)*i->p;
+		if (ch != '\0') {
+			i->p++;
+			i->last_char = ch;
+			return ch;
+		}
+		return EOF;
+	}
+
+	/* FILE-based in_str */
+
+#if ENABLE_FEATURE_EDITING
+	/* This can be stdin, check line editing char[] buffer */
+	if (i->p && *i->p != '\0') {
+		ch = (unsigned char)*i->p++;
+		goto out;
+	}
+#endif
+	/* peek_buf[] is an int array, not char. Can contain EOF. */
+	ch = i->peek_buf[0];
+	if (ch != 0) {
+		int ch2 = i->peek_buf[1];
+		i->peek_buf[0] = ch2;
+		if (ch2 == 0) /* very likely, avoid redundant write */
+			goto out;
+		i->peek_buf[1] = 0;
+		goto out;
+	}
+
+	ch = fgetc_interactive(i);
+ out:
+	debug_printf("file_get: got '%c' %d\n", ch, ch);
+	i->last_char = ch;
+#if ENABLE_HUSH_LINENO_VAR
+	if (ch == '\n') {
+		G.parse_lineno++;
+		debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno);
+	}
+#endif
+	return ch;
+}
+
+static int i_peek(struct in_str *i)
+{
+	int ch;
+
+	if (!i->file) {
+		/* string-based in_str */
+		/* Doesn't report EOF on NUL. None of the callers care. */
+		return (unsigned char)*i->p;
+	}
+
+	/* FILE-based in_str */
+
+#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
+	/* This can be stdin, check line editing char[] buffer */
+	if (i->p && *i->p != '\0')
+		return (unsigned char)*i->p;
+#endif
+	/* peek_buf[] is an int array, not char. Can contain EOF. */
+	ch = i->peek_buf[0];
+	if (ch != 0)
+		return ch;
+
+	/* Need to get a new char */
+	ch = fgetc_interactive(i);
+	debug_printf("file_peek: got '%c' %d\n", ch, ch);
+
+	/* Save it by either rolling back line editing buffer, or in i->peek_buf[0] */
+#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
+	if (i->p) {
+		i->p -= 1;
+		return ch;
+	}
+#endif
+	i->peek_buf[0] = ch;
+	/*i->peek_buf[1] = 0; - already is */
+	return ch;
+}
+
+/* Only ever called if i_peek() was called, and did not return EOF.
+ * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL,
+ * not end-of-line. Therefore we never need to read a new editing line here.
+ */
+static int i_peek2(struct in_str *i)
+{
+	int ch;
+
+	/* There are two cases when i->p[] buffer exists.
+	 * (1) it's a string in_str.
+	 * (2) It's a file, and we have a saved line editing buffer.
+	 * In both cases, we know that i->p[0] exists and not NUL, and
+	 * the peek2 result is in i->p[1].
+	 */
+	if (i->p)
+		return (unsigned char)i->p[1];
+
+	/* Now we know it is a file-based in_str. */
+
+	/* peek_buf[] is an int array, not char. Can contain EOF. */
+	/* Is there 2nd char? */
+	ch = i->peek_buf[1];
+	if (ch == 0) {
+		/* We did not read it yet, get it now */
+		do ch = hfgetc(i->file); while (ch == '\0');
+		i->peek_buf[1] = ch;
+	}
+
+	debug_printf("file_peek2: got '%c' %d\n", ch, ch);
+	return ch;
+}
+
+static int i_getch_and_eat_bkslash_nl(struct in_str *input)
+{
+	for (;;) {
+		int ch, ch2;
+
+		ch = i_getch(input);
+		if (ch != '\\')
+			return ch;
+		ch2 = i_peek(input);
+		if (ch2 != '\n')
+			return ch;
+		/* backslash+newline, skip it */
+		i_getch(input);
+	}
+}
+
+/* Note: this function _eats_ \<newline> pairs, safe to use plain
+ * i_getch() after it instead of i_getch_and_eat_bkslash_nl().
+ */
+static int i_peek_and_eat_bkslash_nl(struct in_str *input)
+{
+	for (;;) {
+		int ch, ch2;
+
+		ch = i_peek(input);
+		if (ch != '\\')
+			return ch;
+		ch2 = i_peek2(input);
+		if (ch2 != '\n')
+			return ch;
+		/* backslash+newline, skip it */
+		i_getch(input);
+		i_getch(input);
+	}
+}
+
+static void setup_file_in_str(struct in_str *i, HFILE *fp)
+{
+	memset(i, 0, sizeof(*i));
+	i->file = fp;
+	/* i->p = NULL; */
+}
+
+static void setup_string_in_str(struct in_str *i, const char *s)
+{
+	memset(i, 0, sizeof(*i));
+	/*i->file = NULL */;
+	i->p = s;
+}
+
+
+/*
+ * o_string support
+ */
+#define B_CHUNK  (32 * sizeof(char*))
+
+static void o_reset_to_empty_unquoted(o_string *o)
+{
+	o->length = 0;
+	o->has_quoted_part = 0;
+	if (o->data)
+		o->data[0] = '\0';
+}
+
+static void o_free_and_set_NULL(o_string *o)
+{
+	free(o->data);
+	memset(o, 0, sizeof(*o));
+}
+
+static ALWAYS_INLINE void o_free(o_string *o)
+{
+	free(o->data);
+}
+
+static void o_grow_by(o_string *o, int len)
+{
+	if (o->length + len > o->maxlen) {
+		o->maxlen += (2 * len) | (B_CHUNK-1);
+		o->data = xrealloc(o->data, 1 + o->maxlen);
+	}
+}
+
+static void o_addchr(o_string *o, int ch)
+{
+	debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
+	if (o->length < o->maxlen) {
+		/* likely. avoid o_grow_by() call */
+ add:
+		o->data[o->length] = ch;
+		o->length++;
+		o->data[o->length] = '\0';
+		return;
+	}
+	o_grow_by(o, 1);
+	goto add;
+}
+
+#if 0
+/* Valid only if we know o_string is not empty */
+static void o_delchr(o_string *o)
+{
+	o->length--;
+	o->data[o->length] = '\0';
+}
+#endif
+
+static void o_addblock(o_string *o, const char *str, int len)
+{
+	o_grow_by(o, len);
+	((char*)mempcpy(&o->data[o->length], str, len))[0] = '\0';
+	o->length += len;
+}
+
+static void o_addstr(o_string *o, const char *str)
+{
+	o_addblock(o, str, strlen(str));
+}
+
+static void o_addstr_with_NUL(o_string *o, const char *str)
+{
+	o_addblock(o, str, strlen(str) + 1);
+}
+
+#if !BB_MMU
+static void nommu_addchr(o_string *o, int ch)
+{
+	if (o)
+		o_addchr(o, ch);
+}
+#else
+# define nommu_addchr(o, str) ((void)0)
+#endif
+
+#if ENABLE_HUSH_MODE_X
+static void x_mode_addchr(int ch)
+{
+	o_addchr(&G.x_mode_buf, ch);
+}
+static void x_mode_addstr(const char *str)
+{
+	o_addstr(&G.x_mode_buf, str);
+}
+static void x_mode_addblock(const char *str, int len)
+{
+	o_addblock(&G.x_mode_buf, str, len);
+}
+static void x_mode_prefix(void)
+{
+	int n = G.x_mode_depth;
+	do x_mode_addchr('+'); while (--n >= 0);
+}
+static void x_mode_flush(void)
+{
+	int len = G.x_mode_buf.length;
+	if (len <= 0)
+		return;
+	if (G.x_mode_fd > 0) {
+		G.x_mode_buf.data[len] = '\n';
+		full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1);
+	}
+	G.x_mode_buf.length = 0;
+}
+#endif
+
+/*
+ * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
+ * Currently, "v='{q,w}'; echo $v" erroneously expands braces in $v.
+ * Apparently, on unquoted $v bash still does globbing
+ * ("v='*.txt'; echo $v" prints all .txt files),
+ * but NOT brace expansion! Thus, there should be TWO independent
+ * quoting mechanisms on $v expansion side: one protects
+ * $v from brace expansion, and other additionally protects "$v" against globbing.
+ * We have only second one.
+ */
+
+#if ENABLE_HUSH_BRACE_EXPANSION
+# define MAYBE_BRACES "{}"
+#else
+# define MAYBE_BRACES ""
+#endif
+
+/* My analysis of quoting semantics tells me that state information
+ * is associated with a destination, not a source.
+ */
+static void o_addqchr(o_string *o, int ch)
+{
+	int sz = 1;
+	/* '-' is included because of this case:
+	 * >filename0 >filename1 >filename9; v='-'; echo filename[0"$v"9]
+	 */
+	char *found = strchr("*?[-\\" MAYBE_BRACES, ch);
+	if (found)
+		sz++;
+	o_grow_by(o, sz);
+	if (found) {
+		o->data[o->length] = '\\';
+		o->length++;
+	}
+	o->data[o->length] = ch;
+	o->length++;
+	o->data[o->length] = '\0';
+}
+
+static void o_addQchr(o_string *o, int ch)
+{
+	int sz = 1;
+	if ((o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)
+	 && strchr("*?[-\\" MAYBE_BRACES, ch)
+	) {
+		sz++;
+		o->data[o->length] = '\\';
+		o->length++;
+	}
+	o_grow_by(o, sz);
+	o->data[o->length] = ch;
+	o->length++;
+	o->data[o->length] = '\0';
+}
+
+static void o_addqblock(o_string *o, const char *str, int len)
+{
+	while (len) {
+		char ch;
+		int sz;
+		int ordinary_cnt = strcspn(str, "*?[-\\" MAYBE_BRACES);
+		if (ordinary_cnt > len) /* paranoia */
+			ordinary_cnt = len;
+		o_addblock(o, str, ordinary_cnt);
+		if (ordinary_cnt == len)
+			return; /* NUL is already added by o_addblock */
+		str += ordinary_cnt;
+		len -= ordinary_cnt + 1; /* we are processing + 1 char below */
+
+		ch = *str++;
+		sz = 1;
+		if (ch) { /* it is necessarily one of "*?[-\\" MAYBE_BRACES */
+			sz++;
+			o->data[o->length] = '\\';
+			o->length++;
+		}
+		o_grow_by(o, sz);
+		o->data[o->length] = ch;
+		o->length++;
+	}
+	o->data[o->length] = '\0';
+}
+
+static void o_addQblock(o_string *o, const char *str, int len)
+{
+	if (!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)) {
+		o_addblock(o, str, len);
+		return;
+	}
+	o_addqblock(o, str, len);
+}
+
+static void o_addQstr(o_string *o, const char *str)
+{
+	o_addQblock(o, str, strlen(str));
+}
+
+/* A special kind of o_string for $VAR and `cmd` expansion.
+ * It contains char* list[] at the beginning, which is grown in 16 element
+ * increments. Actual string data starts at the next multiple of 16 * (char*).
+ * list[i] contains an INDEX (int!) into this string data.
+ * It means that if list[] needs to grow, data needs to be moved higher up
+ * but list[i]'s need not be modified.
+ * NB: remembering how many list[i]'s you have there is crucial.
+ * o_finalize_list() operation post-processes this structure - calculates
+ * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
+ */
+#if DEBUG_EXPAND || DEBUG_GLOB
+static void debug_print_list(const char *prefix, o_string *o, int n)
+{
+	char **list = (char**)o->data;
+	int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+	int i = 0;
+
+	indent();
+	fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n",
+			prefix, list, n, string_start, o->length, o->maxlen,
+			!!(o->o_expflags & EXP_FLAG_GLOB),
+			o->has_quoted_part,
+			!!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+	while (i < n) {
+		indent();
+		fdprintf(2, " list[%d]=%d '%s' %p\n", i, (int)(uintptr_t)list[i],
+				o->data + (int)(uintptr_t)list[i] + string_start,
+				o->data + (int)(uintptr_t)list[i] + string_start);
+		i++;
+	}
+	if (n) {
+		const char *p = o->data + (int)(uintptr_t)list[n - 1] + string_start;
+		indent();
+		fdprintf(2, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+	}
+}
+#else
+# define debug_print_list(prefix, o, n) ((void)0)
+#endif
+
+/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
+ * in list[n] so that it points past last stored byte so far.
+ * It returns n+1. */
+static int o_save_ptr_helper(o_string *o, int n)
+{
+	char **list = (char**)o->data;
+	int string_start;
+	int string_len;
+
+	if (!o->has_empty_slot) {
+		string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+		string_len = o->length - string_start;
+		if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
+			debug_printf_list("list[%d]=%d string_start=%d (growing)\n", n, string_len, string_start);
+			/* list[n] points to string_start, make space for 16 more pointers */
+			o->maxlen += 0x10 * sizeof(list[0]);
+			o->data = xrealloc(o->data, o->maxlen + 1);
+			list = (char**)o->data;
+			memmove(list + n + 0x10, list + n, string_len);
+			/*
+			 * expand_on_ifs() has a "previous argv[] ends in IFS?"
+			 * check. (grep for -prev-ifs-check-).
+			 * Ensure that argv[-1][last] is not garbage
+			 * but zero bytes, to save index check there.
+			 */
+			list[n + 0x10 - 1] = 0;
+			o->length += 0x10 * sizeof(list[0]);
+		} else {
+			debug_printf_list("list[%d]=%d string_start=%d\n",
+					n, string_len, string_start);
+		}
+	} else {
+		/* We have empty slot at list[n], reuse without growth */
+		string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
+		string_len = o->length - string_start;
+		debug_printf_list("list[%d]=%d string_start=%d (empty slot)\n",
+				n, string_len, string_start);
+		o->has_empty_slot = 0;
+	}
+	o->has_quoted_part = 0;
+	list[n] = (char*)(uintptr_t)string_len;
+	return n + 1;
+}
+
+/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
+static int o_get_last_ptr(o_string *o, int n)
+{
+	char **list = (char**)o->data;
+	int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+
+	return ((int)(uintptr_t)list[n-1]) + string_start;
+}
+
+/*
+ * Globbing routines.
+ *
+ * Most words in commands need to be globbed, even ones which are
+ * (single or double) quoted. This stems from the possiblity of
+ * constructs like "abc"* and 'abc'* - these should be globbed.
+ * Having a different code path for fully-quoted strings ("abc",
+ * 'abc') would only help performance-wise, but we still need
+ * code for partially-quoted strings.
+ *
+ * Unfortunately, if we want to match bash and ash behavior in all cases,
+ * the logic can't be "shell-syntax argument is first transformed
+ * to a string, then globbed, and if globbing does not match anything,
+ * it is used verbatim". Here are two examples where it fails:
+ *
+ * 	echo 'b\*'?
+ *
+ * The globbing can't be avoided (because of '?' at the end).
+ * The glob pattern is: b\\\*? - IOW, both \ and * are literals
+ * and are glob-escaped. If this does not match, bash/ash print b\*?
+ * - IOW: they "unbackslash" the glob pattern.
+ * Now, look at this:
+ *
+ * 	v='\\\*'; echo b$v?
+ *
+ * The glob pattern is the same here: b\\\*? - the unquoted $v expansion
+ * should be used as glob pattern with no changes. However, if glob
+ * does not match, bash/ash print b\\\*? - NOT THE SAME as first example!
+ *
+ * ash implements this by having an encoded representation of the word
+ * to glob, which IS NOT THE SAME as the glob pattern - it has more data.
+ * Glob pattern is derived from it. If glob fails, the decision what result
+ * should be is made using that encoded representation. Not glob pattern.
+ */
+
+#if ENABLE_HUSH_BRACE_EXPANSION
+/* There in a GNU extension, GLOB_BRACE, but it is not usable:
+ * first, it processes even {a} (no commas), second,
+ * I didn't manage to make it return strings when they don't match
+ * existing files. Need to re-implement it.
+ */
+
+/* Helper */
+static int glob_needed(const char *s)
+{
+	while (*s) {
+		if (*s == '\\') {
+			if (!s[1])
+				return 0;
+			s += 2;
+			continue;
+		}
+		if (*s == '*' || *s == '[' || *s == '?' || *s == '{')
+			return 1;
+		s++;
+	}
+	return 0;
+}
+/* Return pointer to next closing brace or to comma */
+static const char *next_brace_sub(const char *cp)
+{
+	unsigned depth = 0;
+	cp++;
+	while (*cp != '\0') {
+		if (*cp == '\\') {
+			if (*++cp == '\0')
+				break;
+			cp++;
+			continue;
+		}
+		if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0))
+			break;
+		if (*cp++ == '{')
+			depth++;
+	}
+
+	return *cp != '\0' ? cp : NULL;
+}
+/* Recursive brace globber. Note: may garble pattern[]. */
+static int glob_brace(char *pattern, o_string *o, int n)
+{
+	char *new_pattern_buf;
+	const char *begin;
+	const char *next;
+	const char *rest;
+	const char *p;
+	size_t rest_len;
+
+	debug_printf_glob("glob_brace('%s')\n", pattern);
+
+	begin = pattern;
+	while (1) {
+		if (*begin == '\0')
+			goto simple_glob;
+		if (*begin == '{') {
+			/* Find the first sub-pattern and at the same time
+			 * find the rest after the closing brace */
+			next = next_brace_sub(begin);
+			if (next == NULL) {
+				/* An illegal expression */
+				goto simple_glob;
+			}
+			if (*next == '}') {
+				/* "{abc}" with no commas - illegal
+				 * brace expr, disregard and skip it */
+				begin = next + 1;
+				continue;
+			}
+			break;
+		}
+		if (*begin == '\\' && begin[1] != '\0')
+			begin++;
+		begin++;
+	}
+	debug_printf_glob("begin:%s\n", begin);
+	debug_printf_glob("next:%s\n", next);
+
+	/* Now find the end of the whole brace expression */
+	rest = next;
+	while (*rest != '}') {
+		rest = next_brace_sub(rest);
+		if (rest == NULL) {
+			/* An illegal expression */
+			goto simple_glob;
+		}
+		debug_printf_glob("rest:%s\n", rest);
+	}
+	rest_len = strlen(++rest) + 1;
+
+	/* We are sure the brace expression is well-formed */
+
+	/* Allocate working buffer large enough for our work */
+	new_pattern_buf = xmalloc(strlen(pattern));
+
+	/* We have a brace expression.  BEGIN points to the opening {,
+	 * NEXT points past the terminator of the first element, and REST
+	 * points past the final }.  We will accumulate result names from
+	 * recursive runs for each brace alternative in the buffer using
+	 * GLOB_APPEND.  */
+
+	p = begin + 1;
+	while (1) {
+		/* Construct the new glob expression */
+		memcpy(
+			mempcpy(
+				mempcpy(new_pattern_buf,
+					/* We know the prefix for all sub-patterns */
+					pattern, begin - pattern),
+				p, next - p),
+			rest, rest_len);
+
+		/* Note: glob_brace() may garble new_pattern_buf[].
+		 * That's why we re-copy prefix every time (1st memcpy above).
+		 */
+		n = glob_brace(new_pattern_buf, o, n);
+		if (*next == '}') {
+			/* We saw the last entry */
+			break;
+		}
+		p = next + 1;
+		next = next_brace_sub(next);
+	}
+	free(new_pattern_buf);
+	return n;
+
+ simple_glob:
+	{
+		int gr;
+		glob_t globdata;
+
+		memset(&globdata, 0, sizeof(globdata));
+		gr = glob(pattern, 0, NULL, &globdata);
+		debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+		if (gr != 0) {
+			if (gr == GLOB_NOMATCH) {
+				globfree(&globdata);
+				/* NB: garbles parameter */
+				unbackslash(pattern);
+				o_addstr_with_NUL(o, pattern);
+				debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+				return o_save_ptr_helper(o, n);
+			}
+			if (gr == GLOB_NOSPACE)
+				bb_die_memory_exhausted();
+			/* GLOB_ABORTED? Only happens with GLOB_ERR flag,
+			 * but we didn't specify it. Paranoia again. */
+			bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
+		}
+		if (globdata.gl_pathv && globdata.gl_pathv[0]) {
+			char **argv = globdata.gl_pathv;
+			while (1) {
+				o_addstr_with_NUL(o, *argv);
+				n = o_save_ptr_helper(o, n);
+				argv++;
+				if (!*argv)
+					break;
+			}
+		}
+		globfree(&globdata);
+	}
+	return n;
+}
+/* Performs globbing on last list[],
+ * saving each result as a new list[].
+ */
+static int perform_glob(o_string *o, int n)
+{
+	char *pattern, *copy;
+
+	debug_printf_glob("start perform_glob: n:%d o->data:%p\n", n, o->data);
+	if (!o->data)
+		return o_save_ptr_helper(o, n);
+	pattern = o->data + o_get_last_ptr(o, n);
+	debug_printf_glob("glob pattern '%s'\n", pattern);
+	if (!glob_needed(pattern)) {
+		/* unbackslash last string in o in place, fix length */
+		o->length = unbackslash(pattern) - o->data;
+		debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+		return o_save_ptr_helper(o, n);
+	}
+
+	copy = xstrdup(pattern);
+	/* "forget" pattern in o */
+	o->length = pattern - o->data;
+	n = glob_brace(copy, o, n);
+	free(copy);
+	if (DEBUG_GLOB)
+		debug_print_list("perform_glob returning", o, n);
+	return n;
+}
+
+#else /* !HUSH_BRACE_EXPANSION */
+
+/* Helper */
+static int glob_needed(const char *s)
+{
+	while (*s) {
+		if (*s == '\\') {
+			if (!s[1])
+				return 0;
+			s += 2;
+			continue;
+		}
+		if (*s == '*' || *s == '[' || *s == '?')
+			return 1;
+		s++;
+	}
+	return 0;
+}
+/* Performs globbing on last list[],
+ * saving each result as a new list[].
+ */
+static int perform_glob(o_string *o, int n)
+{
+	glob_t globdata;
+	int gr;
+	char *pattern;
+
+	debug_printf_glob("start perform_glob: n:%d o->data:%p\n", n, o->data);
+	if (!o->data)
+		return o_save_ptr_helper(o, n);
+	pattern = o->data + o_get_last_ptr(o, n);
+	debug_printf_glob("glob pattern '%s'\n", pattern);
+	if (!glob_needed(pattern)) {
+ literal:
+		/* unbackslash last string in o in place, fix length */
+		o->length = unbackslash(pattern) - o->data;
+		debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+		return o_save_ptr_helper(o, n);
+	}
+
+	memset(&globdata, 0, sizeof(globdata));
+	/* Can't use GLOB_NOCHECK: it does not unescape the string.
+	 * If we glob "*.\*" and don't find anything, we need
+	 * to fall back to using literal "*.*", but GLOB_NOCHECK
+	 * will return "*.\*"!
+	 */
+	gr = glob(pattern, 0, NULL, &globdata);
+	debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+	if (gr != 0) {
+		if (gr == GLOB_NOMATCH) {
+			globfree(&globdata);
+			goto literal;
+		}
+		if (gr == GLOB_NOSPACE)
+			bb_die_memory_exhausted();
+		/* GLOB_ABORTED? Only happens with GLOB_ERR flag,
+		 * but we didn't specify it. Paranoia again. */
+		bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
+	}
+	if (globdata.gl_pathv && globdata.gl_pathv[0]) {
+		char **argv = globdata.gl_pathv;
+		/* "forget" pattern in o */
+		o->length = pattern - o->data;
+		while (1) {
+			o_addstr_with_NUL(o, *argv);
+			n = o_save_ptr_helper(o, n);
+			argv++;
+			if (!*argv)
+				break;
+		}
+	}
+	globfree(&globdata);
+	if (DEBUG_GLOB)
+		debug_print_list("perform_glob returning", o, n);
+	return n;
+}
+
+#endif /* !HUSH_BRACE_EXPANSION */
+
+/* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered.
+ * Otherwise, just finish current list[] and start new */
+static int o_save_ptr(o_string *o, int n)
+{
+	if (o->o_expflags & EXP_FLAG_GLOB) {
+		/* If o->has_empty_slot, list[n] was already globbed
+		 * (if it was requested back then when it was filled)
+		 * so don't do that again! */
+		if (!o->has_empty_slot)
+			return perform_glob(o, n); /* o_save_ptr_helper is inside */
+	}
+	return o_save_ptr_helper(o, n);
+}
+
+/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
+static char **o_finalize_list(o_string *o, int n)
+{
+	char **list;
+	int string_start;
+
+	if (DEBUG_EXPAND)
+		debug_print_list("finalized", o, n);
+	debug_printf_expand("finalized n:%d\n", n);
+	list = (char**)o->data;
+	string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+	list[--n] = NULL;
+	while (n) {
+		n--;
+		list[n] = o->data + (int)(uintptr_t)list[n] + string_start;
+	}
+	return list;
+}
+
+static void free_pipe_list(struct pipe *pi);
+
+/* Returns pi->next - next pipe in the list */
+static struct pipe *free_pipe(struct pipe *pi)
+{
+	struct pipe *next;
+	int i;
+
+	debug_printf_clean("free_pipe (pid %d)\n", getpid());
+	for (i = 0; i < pi->num_cmds; i++) {
+		struct command *command;
+		struct redir_struct *r, *rnext;
+
+		command = &pi->cmds[i];
+		debug_printf_clean("  command %d:\n", i);
+		if (command->argv) {
+			if (DEBUG_CLEAN) {
+				int a;
+				char **p;
+				for (a = 0, p = command->argv; *p; a++, p++) {
+					debug_printf_clean("   argv[%d] = %s\n", a, *p);
+				}
+			}
+			free_strings(command->argv);
+			//command->argv = NULL;
+		}
+		/* not "else if": on syntax error, we may have both! */
+		if (command->group) {
+			debug_printf_clean("   begin group (cmd_type:%d)\n",
+					command->cmd_type);
+			free_pipe_list(command->group);
+			debug_printf_clean("   end group\n");
+			//command->group = NULL;
+		}
+		/* else is crucial here.
+		 * If group != NULL, child_func is meaningless */
+#if ENABLE_HUSH_FUNCTIONS
+		else if (command->child_func) {
+			debug_printf_exec("cmd %p releases child func at %p\n", command, command->child_func);
+			command->child_func->parent_cmd = NULL;
+		}
+#endif
+#if !BB_MMU
+		free(command->group_as_string);
+		//command->group_as_string = NULL;
+#endif
+		for (r = command->redirects; r; r = rnext) {
+			debug_printf_clean("   redirect %d%s",
+					r->rd_fd, redir_table[r->rd_type].descrip);
+			/* guard against the case >$FOO, where foo is unset or blank */
+			if (r->rd_filename) {
+				debug_printf_clean(" fname:'%s'\n", r->rd_filename);
+				free(r->rd_filename);
+				//r->rd_filename = NULL;
+			}
+			debug_printf_clean(" rd_dup:%d\n", r->rd_dup);
+			rnext = r->next;
+			free(r);
+		}
+		//command->redirects = NULL;
+	}
+	free(pi->cmds);   /* children are an array, they get freed all at once */
+	//pi->cmds = NULL;
+#if ENABLE_HUSH_JOB
+	free(pi->cmdtext);
+	//pi->cmdtext = NULL;
+#endif
+
+	next = pi->next;
+	free(pi);
+	return next;
+}
+
+static void free_pipe_list(struct pipe *pi)
+{
+	while (pi) {
+#if HAS_KEYWORDS
+		debug_printf_clean("pipe reserved word %d\n", pi->res_word);
+#endif
+		debug_printf_clean("pipe followup code %d\n", pi->followup);
+		pi = free_pipe(pi);
+	}
+}
+
+
+/*** Parsing routines ***/
+
+#ifndef debug_print_tree
+static void debug_print_tree(struct pipe *pi, int lvl)
+{
+	static const char *const PIPE[] = {
+		[PIPE_SEQ] = "SEQ",
+		[PIPE_AND] = "AND",
+		[PIPE_OR ] = "OR" ,
+		[PIPE_BG ] = "BG" ,
+	};
+	static const char *RES[] = {
+		[RES_NONE ] = "NONE" ,
+# if ENABLE_HUSH_IF
+		[RES_IF   ] = "IF"   ,
+		[RES_THEN ] = "THEN" ,
+		[RES_ELIF ] = "ELIF" ,
+		[RES_ELSE ] = "ELSE" ,
+		[RES_FI   ] = "FI"   ,
+# endif
+# if ENABLE_HUSH_LOOPS
+		[RES_FOR  ] = "FOR"  ,
+		[RES_WHILE] = "WHILE",
+		[RES_UNTIL] = "UNTIL",
+		[RES_DO   ] = "DO"   ,
+		[RES_DONE ] = "DONE" ,
+# endif
+# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+		[RES_IN   ] = "IN"   ,
+# endif
+# if ENABLE_HUSH_CASE
+		[RES_CASE ] = "CASE" ,
+		[RES_CASE_IN ] = "CASE_IN" ,
+		[RES_MATCH] = "MATCH",
+		[RES_CASE_BODY] = "CASE_BODY",
+		[RES_ESAC ] = "ESAC" ,
+# endif
+		[RES_XXXX ] = "XXXX" ,
+		[RES_SNTX ] = "SNTX" ,
+	};
+	static const char *const CMDTYPE[] = {
+		"{}",
+		"()",
+		"[noglob]",
+# if ENABLE_HUSH_FUNCTIONS
+		"func()",
+# endif
+	};
+
+	int pin, prn;
+
+	pin = 0;
+	while (pi) {
+		fdprintf(2, "%*spipe %d #cmds:%d %sres_word=%s followup=%d %s\n",
+				lvl*2, "",
+				pin,
+				pi->num_cmds,
+				(IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
+				RES[pi->res_word],
+				pi->followup, PIPE[pi->followup]
+		);
+		prn = 0;
+		while (prn < pi->num_cmds) {
+			struct command *command = &pi->cmds[prn];
+			char **argv = command->argv;
+
+			fdprintf(2, "%*s cmd %d assignment_cnt:%d",
+					lvl*2, "", prn,
+					command->assignment_cnt);
+# if ENABLE_HUSH_LINENO_VAR
+			fdprintf(2, " LINENO:%u", command->lineno);
+# endif
+			if (command->group) {
+				fdprintf(2, " group %s: (argv=%p)%s%s\n",
+						CMDTYPE[command->cmd_type],
+						argv
+# if !BB_MMU
+						, " group_as_string:", command->group_as_string
+# else
+						, "", ""
+# endif
+				);
+				debug_print_tree(command->group, lvl+1);
+				prn++;
+				continue;
+			}
+			if (argv) while (*argv) {
+				fdprintf(2, " '%s'", *argv);
+				argv++;
+			}
+			if (command->redirects)
+				fdprintf(2, " {redir}");
+			fdprintf(2, "\n");
+			prn++;
+		}
+		pi = pi->next;
+		pin++;
+	}
+}
+#endif /* debug_print_tree */
+
+static struct pipe *new_pipe(void)
+{
+	struct pipe *pi;
+	pi = xzalloc(sizeof(struct pipe));
+	/*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */
+	return pi;
+}
+
+/* Command (member of a pipe) is complete, or we start a new pipe
+ * if ctx->command is NULL.
+ * No errors possible here.
+ */
+static int done_command(struct parse_context *ctx)
+{
+	/* The command is really already in the pipe structure, so
+	 * advance the pipe counter and make a new, null command. */
+	struct pipe *pi = ctx->pipe;
+	struct command *command = ctx->command;
+
+#if 0	/* Instead we emit error message at run time */
+	if (ctx->pending_redirect) {
+		/* For example, "cmd >" (no filename to redirect to) */
+		syntax_error("invalid redirect");
+		ctx->pending_redirect = NULL;
+	}
+#endif
+
+	if (command) {
+		if (IS_NULL_CMD(command)) {
+			debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
+			goto clear_and_ret;
+		}
+		pi->num_cmds++;
+		debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
+		//debug_print_tree(ctx->list_head, 20);
+	} else {
+		debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds);
+	}
+
+	/* Only real trickiness here is that the uncommitted
+	 * command structure is not counted in pi->num_cmds. */
+	pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1));
+	ctx->command = command = &pi->cmds[pi->num_cmds];
+ clear_and_ret:
+	memset(command, 0, sizeof(*command));
+#if ENABLE_HUSH_LINENO_VAR
+	command->lineno = G.parse_lineno;
+	debug_printf_parse("command->lineno = G.parse_lineno (%u)\n", G.parse_lineno);
+#endif
+	return pi->num_cmds; /* used only for 0/nonzero check */
+}
+
+static void done_pipe(struct parse_context *ctx, pipe_style type)
+{
+	int not_null;
+
+	debug_printf_parse("done_pipe entered, followup %d\n", type);
+	/* Close previous command */
+	not_null = done_command(ctx);
+#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) {
+		/* 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 this construct, all pipes end with && or ||? */
+		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
+		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;
+		/* for cases like "cmd && &", do not be tricked by last command
+		 * being null - the entire {...} & is NOT null! */
+		not_null = 1;
+	} else {
+ no_conv:
+		ctx->pipe->followup = type;
+	}
+
+	/* Without this check, even just <enter> on command line generates
+	 * tree of three NOPs (!). Which is harmless but annoying.
+	 * IOW: it is safe to do it unconditionally. */
+	if (not_null
+#if ENABLE_HUSH_IF
+	 || ctx->ctx_res_w == RES_FI
+#endif
+#if ENABLE_HUSH_LOOPS
+	 || ctx->ctx_res_w == RES_DONE
+	 || ctx->ctx_res_w == RES_FOR
+	 || ctx->ctx_res_w == RES_IN
+#endif
+#if ENABLE_HUSH_CASE
+	 || ctx->ctx_res_w == RES_ESAC
+#endif
+	) {
+		struct pipe *new_p;
+		debug_printf_parse("done_pipe: adding new pipe: "
+				"not_null:%d ctx->ctx_res_w:%d\n",
+				not_null, ctx->ctx_res_w);
+		new_p = new_pipe();
+		ctx->pipe->next = new_p;
+		ctx->pipe = new_p;
+		/* RES_THEN, RES_DO etc are "sticky" -
+		 * they remain set for pipes inside if/while.
+		 * This is used to control execution.
+		 * RES_FOR and RES_IN are NOT sticky (needed to support
+		 * cases where variable or value happens to match a keyword):
+		 */
+#if ENABLE_HUSH_LOOPS
+		if (ctx->ctx_res_w == RES_FOR
+		 || ctx->ctx_res_w == RES_IN)
+			ctx->ctx_res_w = RES_NONE;
+#endif
+#if ENABLE_HUSH_CASE
+		if (ctx->ctx_res_w == RES_MATCH)
+			ctx->ctx_res_w = RES_CASE_BODY;
+		if (ctx->ctx_res_w == RES_CASE)
+			ctx->ctx_res_w = RES_CASE_IN;
+#endif
+		ctx->command = NULL; /* trick done_command below */
+		/* Create the memory for command, roughly:
+		 * ctx->pipe->cmds = new struct command;
+		 * ctx->command = &ctx->pipe->cmds[0];
+		 */
+		done_command(ctx);
+		//debug_print_tree(ctx->list_head, 10);
+	}
+	debug_printf_parse("done_pipe return\n");
+}
+
+static void initialize_context(struct parse_context *ctx)
+{
+	memset(ctx, 0, sizeof(*ctx));
+	if (MAYBE_ASSIGNMENT != 0)
+		ctx->is_assignment = MAYBE_ASSIGNMENT;
+	ctx->pipe = ctx->list_head = new_pipe();
+	/* Create the memory for command, roughly:
+	 * ctx->pipe->cmds = new struct command;
+	 * ctx->command = &ctx->pipe->cmds[0];
+	 */
+	done_command(ctx);
+}
+
+/* If a reserved word is found and processed, parse context is modified
+ * and 1 is returned.
+ */
+#if HAS_KEYWORDS
+struct reserved_combo {
+	char literal[6];
+	unsigned char res;
+	unsigned char assignment_flag;
+	uint32_t flag;
+};
+enum {
+	FLAG_END   = (1 << RES_NONE ),
+# if ENABLE_HUSH_IF
+	FLAG_IF    = (1 << RES_IF   ),
+	FLAG_THEN  = (1 << RES_THEN ),
+	FLAG_ELIF  = (1 << RES_ELIF ),
+	FLAG_ELSE  = (1 << RES_ELSE ),
+	FLAG_FI    = (1 << RES_FI   ),
+# endif
+# if ENABLE_HUSH_LOOPS
+	FLAG_FOR   = (1 << RES_FOR  ),
+	FLAG_WHILE = (1 << RES_WHILE),
+	FLAG_UNTIL = (1 << RES_UNTIL),
+	FLAG_DO    = (1 << RES_DO   ),
+	FLAG_DONE  = (1 << RES_DONE ),
+	FLAG_IN    = (1 << RES_IN   ),
+# endif
+# if ENABLE_HUSH_CASE
+	FLAG_MATCH = (1 << RES_MATCH),
+	FLAG_ESAC  = (1 << RES_ESAC ),
+# endif
+	FLAG_START = (1 << RES_XXXX ),
+};
+
+static const struct reserved_combo* match_reserved_word(o_string *word)
+{
+	/* Mostly a list of accepted follow-up reserved words.
+	 * FLAG_END means we are done with the sequence, and are ready
+	 * to turn the compound list into a command.
+	 * FLAG_START means the word must start a new compound list.
+	 */
+	static const struct reserved_combo reserved_list[] ALIGN4 = {
+# if ENABLE_HUSH_IF
+		{ "!",     RES_NONE,  NOT_ASSIGNMENT  , 0 },
+		{ "if",    RES_IF,    MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START },
+		{ "then",  RES_THEN,  MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
+		{ "elif",  RES_ELIF,  MAYBE_ASSIGNMENT, FLAG_THEN },
+		{ "else",  RES_ELSE,  MAYBE_ASSIGNMENT, FLAG_FI   },
+		{ "fi",    RES_FI,    NOT_ASSIGNMENT  , FLAG_END  },
+# endif
+# if ENABLE_HUSH_LOOPS
+		{ "for",   RES_FOR,   NOT_ASSIGNMENT  , FLAG_IN | FLAG_DO | FLAG_START },
+		{ "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+		{ "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+		{ "in",    RES_IN,    NOT_ASSIGNMENT  , FLAG_DO   },
+		{ "do",    RES_DO,    MAYBE_ASSIGNMENT, FLAG_DONE },
+		{ "done",  RES_DONE,  NOT_ASSIGNMENT  , FLAG_END  },
+# endif
+# if ENABLE_HUSH_CASE
+		{ "case",  RES_CASE,  NOT_ASSIGNMENT  , FLAG_MATCH | FLAG_START },
+		{ "esac",  RES_ESAC,  NOT_ASSIGNMENT  , FLAG_END  },
+# endif
+	};
+	const struct reserved_combo *r;
+
+	for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
+		if (strcmp(word->data, r->literal) == 0)
+			return r;
+	}
+	return NULL;
+}
+/* Return NULL: not a keyword, else: keyword
+ */
+static const struct reserved_combo* reserved_word(struct parse_context *ctx)
+{
+# if ENABLE_HUSH_CASE
+	static const struct reserved_combo reserved_match = {
+		"",        RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
+	};
+# endif
+	const struct reserved_combo *r;
+
+	if (ctx->word.has_quoted_part)
+		return 0;
+	r = match_reserved_word(&ctx->word);
+	if (!r)
+		return r; /* NULL */
+
+	debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
+# if ENABLE_HUSH_CASE
+	if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) {
+		/* "case word IN ..." - IN part starts first MATCH part */
+		r = &reserved_match;
+	} else
+# endif
+	if (r->flag == 0) { /* '!' */
+		if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
+			syntax_error("! ! command");
+			ctx->ctx_res_w = RES_SNTX;
+		}
+		ctx->ctx_inverted = 1;
+		return r;
+	}
+	if (r->flag & FLAG_START) {
+		struct parse_context *old;
+
+		old = xmemdup(ctx, sizeof(*ctx));
+		debug_printf_parse("push stack %p\n", old);
+		initialize_context(ctx);
+		ctx->stack = old;
+	} else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
+		syntax_error_at(ctx->word.data);
+		ctx->ctx_res_w = RES_SNTX;
+		return r;
+	} else {
+		/* "{...} fi" is ok. "{...} if" is not
+		 * Example:
+		 * if { echo foo; } then { echo bar; } fi */
+		if (ctx->command->group)
+			done_pipe(ctx, PIPE_SEQ);
+	}
+
+	ctx->ctx_res_w = r->res;
+	ctx->old_flag = r->flag;
+	ctx->is_assignment = r->assignment_flag;
+	debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]);
+
+	if (ctx->old_flag & FLAG_END) {
+		struct parse_context *old;
+
+		done_pipe(ctx, PIPE_SEQ);
+		debug_printf_parse("pop stack %p\n", ctx->stack);
+		old = ctx->stack;
+		old->command->group = ctx->list_head;
+		old->command->cmd_type = CMD_NORMAL;
+# if !BB_MMU
+		/* At this point, the compound command's string is in
+		 * ctx->as_string... except for the leading keyword!
+		 * Consider this example: "echo a | if true; then echo a; fi"
+		 * ctx->as_string will contain "true; then echo a; fi",
+		 * with "if " remaining in old->as_string!
+		 */
+		{
+			char *str;
+			int len = old->as_string.length;
+			/* Concatenate halves */
+			o_addstr(&old->as_string, ctx->as_string.data);
+			o_free(&ctx->as_string);
+			/* Find where leading keyword starts in first half */
+			str = old->as_string.data + len;
+			if (str > old->as_string.data)
+				str--; /* skip whitespace after keyword */
+			while (str > old->as_string.data && isalpha(str[-1]))
+				str--;
+			/* Ugh, we're done with this horrid hack */
+			old->command->group_as_string = xstrdup(str);
+			debug_printf_parse("pop, remembering as:'%s'\n",
+					old->command->group_as_string);
+		}
+# endif
+		*ctx = *old;   /* physical copy */
+		free(old);
+	}
+	return r;
+}
+#endif /* HAS_KEYWORDS */
+
+/* Word is complete, look at it and update parsing context.
+ * Normal return is 0. Syntax errors return 1.
+ * Note: on return, word is reset, but not o_free'd!
+ */
+static int done_word(struct parse_context *ctx)
+{
+	struct command *command = ctx->command;
+
+	debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command);
+	if (ctx->word.length == 0 && !ctx->word.has_quoted_part) {
+		debug_printf_parse("done_word return 0: true null, ignored\n");
+		return 0;
+	}
+
+	if (ctx->pending_redirect) {
+		/* We do not glob in e.g. >*.tmp case. bash seems to glob here
+		 * only if run as "bash", not "sh" */
+		/* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
+		 * "2.7 Redirection
+		 * If the redirection operator is "<<" or "<<-", the word
+		 * that follows the redirection operator shall be
+		 * subjected to quote removal; it is unspecified whether
+		 * any of the other expansions occur. For the other
+		 * redirection operators, the word that follows the
+		 * redirection operator shall be subjected to tilde
+		 * expansion, parameter expansion, command substitution,
+		 * arithmetic expansion, and quote removal.
+		 * Pathname expansion shall not be performed
+		 * on the word by a non-interactive shell; an interactive
+		 * shell may perform it, but shall do so only when
+		 * the expansion would result in one word."
+		 */
+//bash does not do parameter/command substitution or arithmetic expansion
+//for _heredoc_ redirection word: these constructs look for exact eof marker
+// as written:
+// <<EOF$t
+// <<EOF$((1))
+// <<EOF`true`  [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug]
+
+		ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data);
+		/* Cater for >\file case:
+		 * >\a creates file a; >\\a, >"\a", >"\\a" create file \a
+		 * Same with heredocs:
+		 * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H
+		 */
+		if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) {
+			unbackslash(ctx->pending_redirect->rd_filename);
+			/* Is it <<"HEREDOC"? */
+			if (ctx->word.has_quoted_part) {
+				ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
+			}
+		}
+		debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data);
+		ctx->pending_redirect = NULL;
+	} else {
+#if HAS_KEYWORDS
+# if ENABLE_HUSH_CASE
+		if (ctx->ctx_dsemicolon
+		 && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */
+		) {
+			/* already done when ctx_dsemicolon was set to 1: */
+			/* ctx->ctx_res_w = RES_MATCH; */
+			ctx->ctx_dsemicolon = 0;
+		} else
+# endif
+# if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+		if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB
+		 && strcmp(ctx->word.data, "]]") == 0
+		) {
+			/* allow "[[ ]] >file" etc */
+			command->cmd_type = CMD_SINGLEWORD_NOGLOB;
+		} else
+# endif
+		if (!command->argv /* if it's the first word... */
+# if ENABLE_HUSH_LOOPS
+		 && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
+		 && ctx->ctx_res_w != RES_IN
+# endif
+# if ENABLE_HUSH_CASE
+		 && ctx->ctx_res_w != RES_CASE
+# endif
+		) {
+			const struct reserved_combo *reserved;
+			reserved = reserved_word(ctx);
+			debug_printf_parse("checking for reserved-ness: %d\n", !!reserved);
+			if (reserved) {
+# if ENABLE_HUSH_LINENO_VAR
+/* Case:
+ * "while ...; do
+ *	cmd ..."
+ * If we don't close the pipe _now_, immediately after "do", lineno logic
+ * sees "cmd" as starting at "do" - i.e., at the previous line.
+ */
+				if (0
+				 IF_HUSH_IF(|| reserved->res == RES_THEN)
+				 IF_HUSH_IF(|| reserved->res == RES_ELIF)
+				 IF_HUSH_IF(|| reserved->res == RES_ELSE)
+				 IF_HUSH_LOOPS(|| reserved->res == RES_DO)
+				) {
+					done_pipe(ctx, PIPE_SEQ);
+				}
+# endif
+				o_reset_to_empty_unquoted(&ctx->word);
+				debug_printf_parse("done_word return %d\n",
+						(ctx->ctx_res_w == RES_SNTX));
+				return (ctx->ctx_res_w == RES_SNTX);
+			}
+# if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+			if (strcmp(ctx->word.data, "[[") == 0) {
+				command->cmd_type = CMD_TEST2_SINGLEWORD_NOGLOB;
+			} else
+# endif
+# if defined(CMD_SINGLEWORD_NOGLOB)
+			if (0
+			/* In bash, local/export/readonly are special, args
+			 * are assignments and therefore expansion of them
+			 * should be "one-word" expansion:
+			 *  $ export i=`echo 'a  b'` # one arg: "i=a  b"
+			 * compare with:
+			 *  $ ls i=`echo 'a  b'`     # two args: "i=a" and "b"
+			 *  ls: cannot access i=a: No such file or directory
+			 *  ls: cannot access b: No such file or directory
+			 * Note: bash 3.2.33(1) does this only if export word
+			 * itself is not quoted:
+			 *  $ export i=`echo 'aaa  bbb'`; echo "$i"
+			 *  aaa  bbb
+			 *  $ "export" i=`echo 'aaa  bbb'`; echo "$i"
+			 *  aaa
+			 */
+			 IF_HUSH_LOCAL(   || strcmp(ctx->word.data, "local") == 0)
+			 IF_HUSH_EXPORT(  || strcmp(ctx->word.data, "export") == 0)
+			 IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0)
+			) {
+				command->cmd_type = CMD_SINGLEWORD_NOGLOB;
+			}
+# else
+			{ /* empty block to pair "if ... else" */ }
+# endif
+		}
+#endif /* HAS_KEYWORDS */
+
+		if (command->group) {
+			/* "{ echo foo; } echo bar" - bad */
+			syntax_error_at(ctx->word.data);
+			debug_printf_parse("done_word return 1: syntax error, "
+					"groups and arglists don't mix\n");
+			return 1;
+		}
+
+		/* If this word wasn't an assignment, next ones definitely
+		 * can't be assignments. Even if they look like ones. */
+		if (ctx->is_assignment != DEFINITELY_ASSIGNMENT
+		 && ctx->is_assignment != WORD_IS_KEYWORD
+		) {
+			ctx->is_assignment = NOT_ASSIGNMENT;
+		} else {
+			if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) {
+				command->assignment_cnt++;
+				debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt);
+			}
+			debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]);
+			ctx->is_assignment = MAYBE_ASSIGNMENT;
+		}
+		debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]);
+		command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data));
+		debug_print_strings("word appended to argv", command->argv);
+	}
+
+#if ENABLE_HUSH_LOOPS
+	if (ctx->ctx_res_w == RES_FOR) {
+		if (ctx->word.has_quoted_part
+		 || endofname(command->argv[0])[0] != '\0'
+		) {
+			/* bash says just "not a valid identifier" */
+			syntax_error("bad variable name in for");
+			return 1;
+		}
+		/* Force FOR to have just one word (variable name) */
+		/* NB: basically, this makes hush see "for v in ..."
+		 * syntax as if it is "for v; in ...". FOR and IN become
+		 * two pipe structs in parse tree. */
+		done_pipe(ctx, PIPE_SEQ);
+	}
+#endif
+#if ENABLE_HUSH_CASE
+	/* Force CASE to have just one word */
+	if (ctx->ctx_res_w == RES_CASE) {
+		done_pipe(ctx, PIPE_SEQ);
+	}
+#endif
+
+	o_reset_to_empty_unquoted(&ctx->word);
+
+	debug_printf_parse("done_word return 0\n");
+	return 0;
+}
+
+
+/* Peek ahead in the input to find out if we have a "&n" construct,
+ * as in "2>&1", that represents duplicating a file descriptor.
+ * Return:
+ * REDIRFD_CLOSE if >&- "close fd" construct is seen,
+ * REDIRFD_SYNTAX_ERR if syntax error,
+ * REDIRFD_TO_FILE if no & was seen,
+ * or the number found.
+ */
+#if BB_MMU
+#define parse_redir_right_fd(as_string, input) \
+	parse_redir_right_fd(input)
+#endif
+static int parse_redir_right_fd(o_string *as_string, struct in_str *input)
+{
+	int ch, d, ok;
+
+	ch = i_peek(input);
+	if (ch != '&')
+		return REDIRFD_TO_FILE;
+
+	ch = i_getch(input);  /* get the & */
+	nommu_addchr(as_string, ch);
+	ch = i_peek(input);
+	if (ch == '-') {
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+		return REDIRFD_CLOSE;
+	}
+	d = 0;
+	ok = 0;
+	while (ch != EOF && isdigit(ch)) {
+		d = d*10 + (ch-'0');
+		ok = 1;
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+		ch = i_peek(input);
+	}
+	if (ok) return d;
+
+//TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2)
+
+	bb_simple_error_msg("ambiguous redirect");
+	return REDIRFD_SYNTAX_ERR;
+}
+
+/* Return code is 0 normal, 1 if a syntax error is detected
+ */
+static int parse_redirect(struct parse_context *ctx,
+		int fd,
+		redir_type style,
+		struct in_str *input)
+{
+	struct command *command = ctx->command;
+	struct redir_struct *redir;
+	struct redir_struct **redirp;
+	int dup_num;
+
+	dup_num = REDIRFD_TO_FILE;
+	if (style != REDIRECT_HEREDOC) {
+		/* Check for a '>&1' type redirect */
+		dup_num = parse_redir_right_fd(&ctx->as_string, input);
+		if (dup_num == REDIRFD_SYNTAX_ERR)
+			return 1;
+	} else {
+		int ch = i_peek_and_eat_bkslash_nl(input);
+		dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */
+		if (dup_num) { /* <<-... */
+			ch = i_getch(input);
+			nommu_addchr(&ctx->as_string, ch);
+			ch = i_peek(input);
+		}
+	}
+
+	if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) {
+		int ch = i_peek_and_eat_bkslash_nl(input);
+		if (ch == '|') {
+			/* >|FILE redirect ("clobbering" >).
+			 * Since we do not support "set -o noclobber" yet,
+			 * >| and > are the same for now. Just eat |.
+			 */
+			ch = i_getch(input);
+			nommu_addchr(&ctx->as_string, ch);
+		}
+	}
+
+	/* Create a new redir_struct and append it to the linked list */
+	redirp = &command->redirects;
+	while ((redir = *redirp) != NULL) {
+		redirp = &(redir->next);
+	}
+	*redirp = redir = xzalloc(sizeof(*redir));
+	/* redir->next = NULL; */
+	/* redir->rd_filename = NULL; */
+	redir->rd_type = style;
+	redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd;
+
+	debug_printf_parse("redirect type %d %s\n", redir->rd_fd,
+				redir_table[style].descrip);
+
+	redir->rd_dup = dup_num;
+	if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) {
+		/* Erik had a check here that the file descriptor in question
+		 * is legit; I postpone that to "run time"
+		 * A "-" representation of "close me" shows up as a -3 here */
+		debug_printf_parse("duplicating redirect '%d>&%d'\n",
+				redir->rd_fd, redir->rd_dup);
+	} else {
+#if 0		/* Instead we emit error message at run time */
+		if (ctx->pending_redirect) {
+			/* For example, "cmd > <file" */
+			syntax_error("invalid redirect");
+		}
+#endif
+		/* Set ctx->pending_redirect, so we know what to do at the
+		 * end of the next parsed word. */
+		ctx->pending_redirect = redir;
+	}
+	return 0;
+}
+
+/* If a redirect is immediately preceded by a number, that number is
+ * supposed to tell which file descriptor to redirect.  This routine
+ * looks for such preceding numbers.  In an ideal world this routine
+ * needs to handle all the following classes of redirects...
+ *     echo 2>foo     # redirects fd  2 to file "foo", nothing passed to echo
+ *     echo 49>foo    # redirects fd 49 to file "foo", nothing passed to echo
+ *     echo -2>foo    # redirects fd  1 to file "foo",    "-2" passed to echo
+ *     echo 49x>foo   # redirects fd  1 to file "foo",   "49x" passed to echo
+ *
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+ * "2.7 Redirection
+ * ... If n is quoted, the number shall not be recognized as part of
+ * the redirection expression. For example:
+ * echo \2>a
+ * writes the character 2 into file a"
+ * We are getting it right by setting ->has_quoted_part on any \<char>
+ *
+ * A -1 return means no valid number was found,
+ * the caller should use the appropriate default for this redirection.
+ */
+static int redirect_opt_num(o_string *o)
+{
+	int num;
+
+	if (o->data == NULL)
+		return -1;
+	num = bb_strtou(o->data, NULL, 10);
+	if (errno || num < 0)
+		return -1;
+	o_reset_to_empty_unquoted(o);
+	return num;
+}
+
+#if BB_MMU
+#define fetch_till_str(as_string, input, word, skip_tabs) \
+	fetch_till_str(input, word, skip_tabs)
+#endif
+static char *fetch_till_str(o_string *as_string,
+		struct in_str *input,
+		const char *word,
+		int heredoc_flags)
+{
+	o_string heredoc = NULL_O_STRING;
+	unsigned past_EOL;
+	int prev = 0; /* not \ */
+	int ch;
+
+	/* Starting with "" is necessary for this case:
+	 * cat <<EOF
+	 *
+	 * xxx
+	 * EOF
+	 */
+	heredoc.data = xzalloc(1); /* start as "", not as NULL */
+
+	goto jump_in;
+
+	while (1) {
+		ch = i_getch(input);
+		if (ch != EOF)
+			nommu_addchr(as_string, ch);
+		if (ch == '\n' || ch == EOF) {
+ check_heredoc_end:
+			if ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\') {
+				/* End-of-line, and not a line continuation */
+				if (strcmp(heredoc.data + past_EOL, word) == 0) {
+					heredoc.data[past_EOL] = '\0';
+					debug_printf_heredoc("parsed '%s' heredoc '%s'\n", word, heredoc.data);
+					return heredoc.data;
+				}
+				if (ch == '\n') {
+					/* This is a new line.
+					 * Remember position and backslash-escaping status.
+					 */
+					o_addchr(&heredoc, ch);
+					prev = ch;
+ jump_in:
+					past_EOL = heredoc.length;
+					/* Get 1st char of next line, possibly skipping leading tabs */
+					do {
+						ch = i_getch(input);
+						if (ch != EOF)
+							nommu_addchr(as_string, ch);
+					} while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t');
+					/* If this immediately ended the line,
+					 * go back to end-of-line checks.
+					 */
+					if (ch == '\n')
+						goto check_heredoc_end;
+				}
+			} else {
+				/* Backslash-line continuation in an unquoted
+				 * heredoc. This does not need special handling
+				 * for heredoc body (unquoted heredocs are
+				 * expanded on "execution" and that would take
+				 * care of this case too), but not the case
+				 * of line continuation *in terminator*:
+				 *  cat <<EOF
+				 *  Ok1
+				 *  EO\
+				 *  F
+				 */
+				heredoc.data[--heredoc.length] = '\0';
+				prev = 0; /* not '\' */
+				continue;
+			}
+		}
+		if (ch == EOF) {
+			o_free(&heredoc);
+			return NULL; /* error */
+		}
+		o_addchr(&heredoc, ch);
+		nommu_addchr(as_string, ch);
+		if (prev == '\\' && ch == '\\')
+			/* Correctly handle foo\\<eol> (not a line cont.) */
+			prev = 0; /* not '\' */
+		else
+			prev = ch;
+	}
+}
+
+/* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs
+ * and load them all. There should be exactly heredoc_cnt of them.
+ */
+#if BB_MMU
+#define fetch_heredocs(as_string, pi, heredoc_cnt, input) \
+	fetch_heredocs(pi, heredoc_cnt, input)
+#endif
+static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt, struct in_str *input)
+{
+	while (pi && heredoc_cnt) {
+		int i;
+		struct command *cmd = pi->cmds;
+
+		debug_printf_heredoc("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n",
+				pi->num_cmds,
+				cmd->argv ? cmd->argv[0] : "NONE"
+		);
+		for (i = 0; i < pi->num_cmds; i++) {
+			struct redir_struct *redir = cmd->redirects;
+
+			debug_printf_heredoc("fetch_heredocs: %d cmd argv0:'%s'\n",
+					i, cmd->argv ? cmd->argv[0] : "NONE");
+			while (redir) {
+				if (redir->rd_type == REDIRECT_HEREDOC) {
+					char *p;
+
+					redir->rd_type = REDIRECT_HEREDOC2;
+					/* redir->rd_dup is (ab)used to indicate <<- */
+					p = fetch_till_str(as_string, input,
+							redir->rd_filename, redir->rd_dup);
+					if (!p) {
+						syntax_error("unexpected EOF in here document");
+						return -1;
+					}
+					free(redir->rd_filename);
+					redir->rd_filename = p;
+					heredoc_cnt--;
+				}
+				redir = redir->next;
+			}
+			if (cmd->group) {
+				//bb_error_msg("%s:%u heredoc_cnt:%d", __func__, __LINE__, heredoc_cnt);
+				heredoc_cnt = fetch_heredocs(as_string, cmd->group, heredoc_cnt, input);
+				//bb_error_msg("%s:%u heredoc_cnt:%d", __func__, __LINE__, heredoc_cnt);
+				if (heredoc_cnt < 0)
+					return heredoc_cnt; /* error */
+			}
+			cmd++;
+		}
+		pi = pi->next;
+	}
+	return heredoc_cnt;
+}
+
+
+static int run_list(struct pipe *pi);
+#if BB_MMU
+#define parse_stream(pstring, heredoc_cnt_ptr, input, end_trigger) \
+	parse_stream(heredoc_cnt_ptr, input, end_trigger)
+#endif
+static struct pipe *parse_stream(char **pstring,
+		int *heredoc_cnt_ptr,
+		struct in_str *input,
+		int end_trigger);
+
+/* Returns number of heredocs not yet consumed,
+ * or -1 on error.
+ */
+static int parse_group(struct parse_context *ctx,
+		struct in_str *input, int ch)
+{
+	/* ctx->word contains characters seen prior to ( or {.
+	 * Typically it's empty, but for function defs,
+	 * it contains function name (without '()'). */
+#if BB_MMU
+# define as_string NULL
+#else
+	char *as_string = NULL;
+#endif
+	struct pipe *pipe_list;
+	int heredoc_cnt = 0;
+	int endch;
+	struct command *command = ctx->command;
+
+	debug_printf_parse("parse_group entered\n");
+#if ENABLE_HUSH_FUNCTIONS
+	if (ch == '(' && !ctx->word.has_quoted_part) {
+		if (ctx->word.length)
+			if (done_word(ctx))
+				return -1;
+		if (!command->argv)
+			goto skip; /* (... */
+		if (command->argv[1]) { /* word word ... (... */
+			syntax_error_unexpected_ch('(');
+			return -1;
+		}
+		/* it is "word(..." or "word (..." */
+		do
+			ch = i_getch(input);
+		while (ch == ' ' || ch == '\t');
+		if (ch != ')') {
+			syntax_error_unexpected_ch(ch);
+			return -1;
+		}
+		nommu_addchr(&ctx->as_string, ch);
+		do
+			ch = i_getch(input);
+		while (ch == ' ' || ch == '\t' || ch == '\n');
+		if (ch != '{' && ch != '(') {
+			syntax_error_unexpected_ch(ch);
+			return -1;
+		}
+		nommu_addchr(&ctx->as_string, ch);
+		command->cmd_type = CMD_FUNCDEF;
+		goto skip;
+	}
+#endif
+
+#if 0 /* Prevented by caller */
+	if (command->argv /* word [word]{... */
+	 || ctx->word.length /* word{... */
+	 || ctx->word.has_quoted_part /* ""{... */
+	) {
+		syntax_error(NULL);
+		debug_printf_parse("parse_group return -1: "
+			"syntax error, groups and arglists don't mix\n");
+		return -1;
+	}
+#endif
+
+ IF_HUSH_FUNCTIONS(skip:)
+
+	endch = '}';
+	if (ch == '(') {
+		endch = ')';
+		IF_HUSH_FUNCTIONS(if (command->cmd_type != CMD_FUNCDEF))
+			command->cmd_type = CMD_SUBSHELL;
+	} else {
+		/* bash does not allow "{echo...", requires whitespace */
+		ch = i_peek(input);
+		if (ch != ' ' && ch != '\t' && ch != '\n'
+		 && ch != '('	/* but "{(..." is allowed (without whitespace) */
+		) {
+			syntax_error_unexpected_ch(ch);
+			return -1;
+		}
+		if (ch != '(') {
+			ch = i_getch(input);
+			nommu_addchr(&ctx->as_string, ch);
+		}
+	}
+
+	debug_printf_heredoc("calling parse_stream, heredoc_cnt:%d\n", heredoc_cnt);
+	pipe_list = parse_stream(&as_string, &heredoc_cnt, input, endch);
+	debug_printf_heredoc("parse_stream returned: heredoc_cnt:%d\n", heredoc_cnt);
+#if !BB_MMU
+	if (as_string)
+		o_addstr(&ctx->as_string, as_string);
+#endif
+
+	/* empty ()/{} or parse error? */
+	if (!pipe_list || pipe_list == ERR_PTR) {
+		/* parse_stream already emitted error msg */
+		if (!BB_MMU)
+			free(as_string);
+		debug_printf_parse("parse_group return -1: "
+			"parse_stream returned %p\n", pipe_list);
+		return -1;
+	}
+#if !BB_MMU
+	as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */
+	command->group_as_string = as_string;
+	debug_printf_parse("end of group, remembering as:'%s'\n",
+			command->group_as_string);
+#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+	/* Convert "f() (cmds)" to "f() {(cmds)}" */
+	if (command->cmd_type == CMD_FUNCDEF && endch == ')') {
+		struct command *cmd2;
+
+		cmd2 = xzalloc(sizeof(*cmd2));
+		cmd2->cmd_type = CMD_SUBSHELL;
+		cmd2->group = pipe_list;
+# if !BB_MMU
+//UNTESTED!
+		cmd2->group_as_string = command->group_as_string;
+		command->group_as_string = xasprintf("(%s)", command->group_as_string);
+# endif
+
+		pipe_list = new_pipe();
+		pipe_list->cmds = cmd2;
+		pipe_list->num_cmds = 1;
+	}
+#endif
+
+	command->group = pipe_list;
+
+	debug_printf_parse("parse_group return %d\n", heredoc_cnt);
+	return heredoc_cnt;
+	/* command remains "open", available for possible redirects */
+#undef as_string
+}
+
+#if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS
+/* Subroutines for copying $(...) and `...` things */
+/* '...' */
+static int add_till_single_quote(o_string *dest, struct in_str *input)
+{
+	while (1) {
+		int ch = i_getch(input);
+		if (ch == EOF) {
+			syntax_error_unterm_ch('\'');
+			return 0;
+		}
+		if (ch == '\'')
+			return 1;
+		o_addchr(dest, ch);
+	}
+}
+static int add_till_single_quote_dquoted(o_string *dest, struct in_str *input)
+{
+	while (1) {
+		int ch = i_getch(input);
+		if (ch == EOF) {
+			syntax_error_unterm_ch('\'');
+			return 0;
+		}
+		if (ch == '\'')
+			return 1;
+		o_addqchr(dest, ch);
+	}
+}
+/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
+static int add_till_double_quote(o_string *dest, struct in_str *input)
+{
+	while (1) {
+		int ch = i_getch(input);
+		if (ch == EOF) {
+			syntax_error_unterm_ch('"');
+			return 0;
+		}
+		if (ch == '"')
+			return 1;
+		if (ch == '\\') {  /* \x. Copy both chars. */
+			o_addchr(dest, ch);
+			ch = i_getch(input);
+		}
+		o_addchr(dest, ch);
+		if (ch == '`') {
+			if (!add_till_backquote(dest, input, /*in_dquote:*/ 1))
+				return 0;
+			o_addchr(dest, ch);
+			continue;
+		}
+		//if (ch == '$') ...
+	}
+}
+/* Process `cmd` - copy contents until "`" is seen. Complicated by
+ * \` quoting.
+ * "Within the backquoted style of command substitution, backslash
+ * shall retain its literal meaning, except when followed by: '$', '`', or '\'.
+ * The search for the matching backquote shall be satisfied by the first
+ * backquote found without a preceding backslash; during this search,
+ * if a non-escaped backquote is encountered within a shell comment,
+ * a here-document, an embedded command substitution of the $(command)
+ * form, or a quoted string, undefined results occur. A single-quoted
+ * or double-quoted string that begins, but does not end, within the
+ * "`...`" sequence produces undefined results."
+ * Example                               Output
+ * echo `echo '\'TEST\`echo ZZ\`BEST`    \TESTZZBEST
+ */
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
+{
+	while (1) {
+		int ch = i_getch(input);
+		if (ch == '`')
+			return 1;
+		if (ch == '\\') {
+			/* \x. Copy both unless it is \`, \$, \\ and maybe \" */
+			ch = i_getch(input);
+			if (ch != '`'
+			 && ch != '$'
+			 && ch != '\\'
+			 && (!in_dquote || ch != '"')
+			) {
+				o_addchr(dest, '\\');
+			}
+		}
+		if (ch == EOF) {
+			syntax_error_unterm_ch('`');
+			return 0;
+		}
+		o_addchr(dest, ch);
+	}
+}
+/* Process $(cmd) - copy contents until ")" is seen. Complicated by
+ * quoting and nested ()s.
+ * "With the $(command) style of command substitution, all characters
+ * following the open parenthesis to the matching closing parenthesis
+ * constitute the command. Any valid shell script can be used for command,
+ * except a script consisting solely of redirections which produces
+ * unspecified results."
+ * Example                              Output
+ * echo $(echo '(TEST)' BEST)           (TEST) BEST
+ * echo $(echo 'TEST)' BEST)            TEST) BEST
+ * echo $(echo \(\(TEST\) BEST)         ((TEST) BEST
+ *
+ * Also adapted to eat ${var%...} and $((...)) constructs, since ... part
+ * can contain arbitrary constructs, just like $(cmd).
+ * In bash compat mode, it needs to also be able to stop on ':' or '/'
+ * for ${var:N[:M]} and ${var/P[/R]} parsing.
+ */
+#define DOUBLE_CLOSE_CHAR_FLAG 0x80
+static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch)
+{
+	int ch;
+	char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG;
+# if BASH_SUBSTR || BASH_PATTERN_SUBST
+	char end_char2 = end_ch >> 8;
+# endif
+	end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1);
+
+# if ENABLE_HUSH_INTERACTIVE
+	G.promptmode = 1; /* PS2 */
+# endif
+	debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
+
+	while (1) {
+		ch = i_getch(input);
+		if (ch == EOF) {
+			syntax_error_unterm_ch(end_ch);
+			return 0;
+		}
+		if (ch == end_ch
+# if BASH_SUBSTR || BASH_PATTERN_SUBST
+		 || ch == end_char2
+# endif
+		) {
+			if (!dbl)
+				break;
+			/* we look for closing )) of $((EXPR)) */
+			if (i_peek_and_eat_bkslash_nl(input) == end_ch) {
+				i_getch(input); /* eat second ')' */
+				break;
+			}
+		}
+		o_addchr(dest, ch);
+		//bb_error_msg("%s:o_addchr('%c')", __func__, ch);
+		if (ch == '(' || ch == '{') {
+			ch = (ch == '(' ? ')' : '}');
+			if (!add_till_closing_bracket(dest, input, ch))
+				return 0;
+			o_addchr(dest, ch);
+			continue;
+		}
+		if (ch == '\'') {
+			if (!add_till_single_quote(dest, input))
+				return 0;
+			o_addchr(dest, ch);
+			continue;
+		}
+		if (ch == '"') {
+			if (!add_till_double_quote(dest, input))
+				return 0;
+			o_addchr(dest, ch);
+			continue;
+		}
+		if (ch == '`') {
+			if (!add_till_backquote(dest, input, /*in_dquote:*/ 0))
+				return 0;
+			o_addchr(dest, ch);
+			continue;
+		}
+		if (ch == '\\') {
+			/* \x. Copy verbatim. Important for  \(, \) */
+			ch = i_getch(input);
+			if (ch == EOF) {
+				syntax_error_unterm_ch(end_ch);
+				return 0;
+			}
+# if 0
+			if (ch == '\n') {
+				/* "backslash+newline", ignore both */
+				o_delchr(dest); /* undo insertion of '\' */
+				continue;
+			}
+# endif
+			o_addchr(dest, ch);
+			//bb_error_msg("%s:o_addchr('%c') after '\\'", __func__, ch);
+			continue;
+		}
+	}
+	debug_printf_parse("%s return '%s' ch:'%c'\n", __func__, dest->data, ch);
+	return ch;
+}
+#endif /* ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS */
+
+#if BASH_DOLLAR_SQUOTE
+/* Return code: 1 for "found and parsed", 0 for "seen something else" */
+# if BB_MMU
+#define parse_dollar_squote(as_string, dest, input) \
+	parse_dollar_squote(dest, input)
+#define as_string NULL
+# endif
+static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_str *input)
+{
+	int start;
+	int ch = i_peek_and_eat_bkslash_nl(input);  /* first character after the $ */
+	debug_printf_parse("parse_dollar_squote entered: ch='%c'\n", ch);
+	if (ch != '\'')
+		return 0;
+
+	dest->has_quoted_part = 1;
+	start = dest->length;
+
+	ch = i_getch(input); /* eat ' */
+	nommu_addchr(as_string, ch);
+	while (1) {
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+		if (ch == EOF) {
+			syntax_error_unterm_ch('\'');
+			return 0;
+		}
+		if (ch == '\'')
+			break;
+		if (ch == SPECIAL_VAR_SYMBOL) {
+			/* Convert raw ^C to corresponding special variable reference */
+			o_addchr(dest, SPECIAL_VAR_SYMBOL);
+			o_addchr(dest, SPECIAL_VAR_QUOTED_SVS);
+			/* will addchr() another SPECIAL_VAR_SYMBOL (see after the if() block) */
+		} else if (ch == '\\') {
+			static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567";
+
+			ch = i_getch(input);
+			nommu_addchr(as_string, ch);
+			if (strchr(C_escapes, ch)) {
+				char buf[4];
+				char *p = buf;
+				int cnt = 2;
+
+				buf[0] = ch;
+				if ((unsigned char)(ch - '0') <= 7) { /* \ooo */
+					do {
+						ch = i_peek(input);
+						if ((unsigned char)(ch - '0') > 7)
+							break;
+						*++p = ch = i_getch(input);
+						nommu_addchr(as_string, ch);
+					} while (--cnt != 0);
+				} else if (ch == 'x') { /* \xHH */
+					do {
+						ch = i_peek(input);
+						if (!isxdigit(ch))
+							break;
+						*++p = ch = i_getch(input);
+						nommu_addchr(as_string, ch);
+					} while (--cnt != 0);
+					if (cnt == 2) { /* \x but next char is "bad" */
+						ch = 'x';
+						goto unrecognized;
+					}
+				} /* else simple seq like \\ or \t */
+				*++p = '\0';
+				p = buf;
+				ch = bb_process_escape_sequence((void*)&p);
+				//bb_error_msg("buf:'%s' ch:%x", buf, ch);
+				if (ch == '\0')
+					continue; /* bash compat: $'...\0...' emits nothing */
+			} else { /* unrecognized "\z": encode both chars unless ' or " */
+				if (ch != '\'' && ch != '"') {
+ unrecognized:
+					o_addqchr(dest, '\\');
+				}
+			}
+		} /* if (\...) */
+		o_addqchr(dest, ch);
+	}
+
+	if (dest->length == start) {
+		/* $'', $'\0', $'\000\x00' and the like */
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+	}
+
+	return 1;
+# undef as_string
+}
+#else
+# #define parse_dollar_squote(as_string, dest, input) 0
+#endif /* BASH_DOLLAR_SQUOTE */
+
+/* Return code: 0 for OK, 1 for syntax error */
+#if BB_MMU
+#define parse_dollar(as_string, dest, input, quote_mask) \
+	parse_dollar(dest, input, quote_mask)
+#define as_string NULL
+#endif
+static int parse_dollar(o_string *as_string,
+		o_string *dest,
+		struct in_str *input, unsigned char quote_mask)
+{
+	int ch = i_peek_and_eat_bkslash_nl(input);  /* first character after the $ */
+
+	debug_printf_parse("parse_dollar entered: ch='%c' quote_mask:0x%x\n", ch, quote_mask);
+	if (isalpha(ch)) {
+ make_var:
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+ /*make_var1:*/
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		while (1) {
+			debug_printf_parse(": '%c'\n", ch);
+			o_addchr(dest, ch | quote_mask);
+			quote_mask = 0;
+			ch = i_peek_and_eat_bkslash_nl(input);
+			if (!isalnum(ch) && ch != '_') {
+				/* End of variable name reached */
+				break;
+			}
+			ch = i_getch(input);
+			nommu_addchr(as_string, ch);
+		}
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+	} else if (isdigit(ch)) {
+ make_one_char_var:
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		debug_printf_parse(": '%c'\n", ch);
+		o_addchr(dest, ch | quote_mask);
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+	} else switch (ch) {
+	case '$': /* pid */
+	case '!': /* last bg pid */
+	case '?': /* last exit code */
+	case '#': /* number of args */
+	case '*': /* args */
+	case '@': /* args */
+	case '-': /* $- option flags set by set builtin or shell options (-i etc) */
+		goto make_one_char_var;
+	case '{': {
+		char len_single_ch;
+
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+
+		ch = i_getch(input); /* eat '{' */
+		nommu_addchr(as_string, ch);
+
+		ch = i_getch_and_eat_bkslash_nl(input); /* first char after '{' */
+		/* It should be ${?}, or ${#var},
+		 * or even ${?+subst} - operator acting on a special variable,
+		 * or the beginning of variable name.
+		 */
+		if (ch == EOF
+		 || (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) /* not one of those */
+		) {
+ bad_dollar_syntax:
+			syntax_error_unterm_str("${name}");
+			debug_printf_parse("parse_dollar return 0: unterminated ${name}\n");
+			return 0;
+		}
+		nommu_addchr(as_string, ch);
+		len_single_ch = ch;
+		ch |= quote_mask;
+
+		/* It's possible to just call add_till_closing_bracket() at this point.
+		 * However, this regresses some of our testsuite cases
+		 * which check invalid constructs like ${%}.
+		 * Oh well... let's check that the var name part is fine... */
+
+		if (isdigit(len_single_ch)
+		 || (len_single_ch == '#' && isdigit(i_peek_and_eat_bkslash_nl(input)))
+		) {
+			/* Execution engine uses plain xatoi_positive()
+			 * to interpret ${NNN} and {#NNN},
+			 * check syntax here in the parser.
+			 * (bash does not support expressions in ${#NN},
+			 * e.g. ${#$var} and {#1:+WORD} are not supported).
+			 */
+			unsigned cnt = 9; /* max 9 digits for ${NN} and 8 for {#NN} */
+			while (1) {
+				o_addchr(dest, ch);
+				debug_printf_parse(": '%c'\n", ch);
+				ch = i_getch_and_eat_bkslash_nl(input);
+				nommu_addchr(as_string, ch);
+				if (ch == '}')
+					break;
+				if (--cnt == 0)
+					goto bad_dollar_syntax;
+				if (len_single_ch != '#' && strchr(VAR_SUBST_OPS, ch))
+					/* ${NN<op>...} is valid */
+					goto eat_until_closing;
+				if (!isdigit(ch))
+					goto bad_dollar_syntax;
+			}
+		} else
+		while (1) {
+			unsigned pos;
+
+			o_addchr(dest, ch);
+			debug_printf_parse(": '%c'\n", ch);
+
+			ch = i_getch(input);
+			nommu_addchr(as_string, ch);
+			if (ch == '}')
+				break;
+			if (!isalnum(ch) && ch != '_') {
+				unsigned end_ch;
+				unsigned char last_ch;
+				/* handle parameter expansions
+				 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
+				 */
+				if (!strchr(VAR_SUBST_OPS, ch)) { /* ${var<bad_char>... */
+					if (len_single_ch != '#'
+					/*|| !strchr(SPECIAL_VARS_STR, ch) - disallow errors like ${#+} ? */
+					 || i_peek(input) != '}'
+					) {
+						goto bad_dollar_syntax;
+					}
+					/* else: it's "length of C" ${#C} op,
+					 * where C is a single char
+					 * special var name, e.g. ${#!}.
+					 */
+				}
+ eat_until_closing:
+				/* Eat everything until closing '}' (or ':') */
+				end_ch = '}';
+				if (BASH_SUBSTR
+				 && ch == ':'
+				 && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input))
+				) {
+					/* It's ${var:N[:M]} thing */
+					end_ch = '}' * 0x100 + ':';
+				}
+				if (BASH_PATTERN_SUBST
+				 && ch == '/'
+				) {
+					/* It's ${var/[/]pattern[/repl]} thing */
+					if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */
+						i_getch(input);
+						nommu_addchr(as_string, '/');
+						ch = '\\';
+					}
+					end_ch = '}' * 0x100 + '/';
+				}
+				o_addchr(dest, ch);
+				/* The pattern can't be empty.
+				 * IOW: if the first char after "${v//" is a slash,
+				 * it does not terminate the pattern - it's the first char of the pattern:
+				 *  v=/dev/ram; echo ${v////-}  prints -dev-ram (pattern is "/")
+				 *  v=/dev/ram; echo ${v///r/-} prints /dev-am  (pattern is "/r")
+				 */
+				if (i_peek(input) == '/') {
+					o_addchr(dest, i_getch(input));
+				}
+ again:
+				if (!BB_MMU)
+					pos = dest->length;
+#if ENABLE_HUSH_DOLLAR_OPS
+				last_ch = add_till_closing_bracket(dest, input, end_ch);
+				if (last_ch == 0) /* error? */
+					return 0;
+#else
+# error Simple code to only allow ${var} is not implemented
+#endif
+				if (as_string) {
+					o_addstr(as_string, dest->data + pos);
+					o_addchr(as_string, last_ch);
+				}
+
+				if ((BASH_SUBSTR || BASH_PATTERN_SUBST)
+					 && (end_ch & 0xff00)
+				) {
+					/* close the first block: */
+					o_addchr(dest, SPECIAL_VAR_SYMBOL);
+					/* while parsing N from ${var:N[:M]}
+					 * or pattern from ${var/[/]pattern[/repl]} */
+					if ((end_ch & 0xff) == last_ch) {
+						/* got ':' or '/'- parse the rest */
+						end_ch = '}';
+						goto again;
+					}
+					/* got '}' */
+					if (BASH_SUBSTR && end_ch == '}' * 0x100 + ':') {
+						/* it's ${var:N} - emulate :999999999 */
+						o_addstr(dest, "999999999");
+					} /* else: it's ${var/[/]pattern} */
+				}
+				break;
+			}
+			len_single_ch = 0; /* it can't be ${#C} op */
+		}
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		break;
+	}
+#if ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_TICK
+	case '(': {
+		unsigned pos;
+
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+# if ENABLE_FEATURE_SH_MATH
+		if (i_peek_and_eat_bkslash_nl(input) == '(') {
+			ch = i_getch(input);
+			nommu_addchr(as_string, ch);
+			o_addchr(dest, SPECIAL_VAR_SYMBOL);
+			o_addchr(dest, quote_mask | '+');
+			if (!BB_MMU)
+				pos = dest->length;
+			if (!add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG))
+				return 0; /* error */
+			if (as_string) {
+				o_addstr(as_string, dest->data + pos);
+				o_addchr(as_string, ')');
+				o_addchr(as_string, ')');
+			}
+			o_addchr(dest, SPECIAL_VAR_SYMBOL);
+			break;
+		}
+# endif
+# if ENABLE_HUSH_TICK
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		o_addchr(dest, quote_mask | '`');
+		if (!BB_MMU)
+			pos = dest->length;
+		if (!add_till_closing_bracket(dest, input, ')'))
+			return 0; /* error */
+		if (as_string) {
+			o_addstr(as_string, dest->data + pos);
+			o_addchr(as_string, ')');
+		}
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+# endif
+		break;
+	}
+#endif
+	case '_':
+		goto make_var;
+#if 0
+	/* TODO: $_: */
+	/* $_ Shell or shell script name; or last argument of last command
+	 * (if last command wasn't a pipe; if it was, bash sets $_ to "");
+	 * but in command's env, set to full pathname used to invoke it */
+		ch = i_getch(input);
+		nommu_addchr(as_string, ch);
+		ch = i_peek_and_eat_bkslash_nl(input);
+		if (isalnum(ch)) { /* it's $_name or $_123 */
+			ch = '_';
+			goto make_var1;
+		}
+		/* else: it's $_ */
+#endif
+	default:
+		o_addQchr(dest, '$');
+	}
+	debug_printf_parse("parse_dollar return 1 (ok)\n");
+	return 1;
+#undef as_string
+}
+
+#if BB_MMU
+#define encode_string(as_string, dest, input, dquote_end) \
+	encode_string(dest, input, dquote_end)
+#define as_string NULL
+#endif
+static int encode_string(o_string *as_string,
+		o_string *dest,
+		struct in_str *input,
+		int dquote_end)
+{
+	int ch;
+	int next;
+
+ again:
+	ch = i_getch(input);
+	if (ch != EOF)
+		nommu_addchr(as_string, ch);
+	if (ch == dquote_end) { /* may be only '"' or EOF */
+		debug_printf_parse("encode_string return 1 (ok)\n");
+		return 1;
+	}
+	/* note: can't move it above ch == dquote_end check! */
+	if (ch == EOF) {
+		syntax_error_unterm_ch('"');
+		return 0; /* error */
+	}
+	next = '\0';
+	if (ch != '\n') {
+		next = i_peek(input);
+	}
+	debug_printf_parse("\" ch=%c (%d) escape=%d\n",
+			ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+	if (ch == '\\') {
+		if (next == EOF) {
+			/* Testcase: in interactive shell a file with
+			 *  echo "unterminated string\<eof>
+			 * is sourced.
+			 */
+			syntax_error_unterm_ch('"');
+			return 0; /* error */
+		}
+		/* bash:
+		 * "The backslash retains its special meaning [in "..."]
+		 * only when followed by one of the following characters:
+		 * $, `, ", \, or <newline>.  A double quote may be quoted
+		 * within double quotes by preceding it with a backslash."
+		 * NB: in (unquoted) heredoc, above does not apply to ",
+		 * therefore we check for it by "next == dquote_end" cond.
+		 */
+		if (next == dquote_end || strchr("$`\\\n", next)) {
+			ch = i_getch(input); /* eat next */
+			if (ch == '\n')
+				goto again; /* skip \<newline> */
+		} /* else: ch remains == '\\', and we double it below: */
+		o_addqchr(dest, ch); /* \c if c is a glob char, else just c */
+		nommu_addchr(as_string, ch);
+		goto again;
+	}
+	if (ch == '$') {
+		//if (parse_dollar_squote(as_string, dest, input))
+		//	goto again;
+		if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) {
+			debug_printf_parse("encode_string return 0: "
+					"parse_dollar returned 0 (error)\n");
+			return 0;
+		}
+		goto again;
+	}
+#if ENABLE_HUSH_TICK
+	if (ch == '`') {
+		//unsigned pos = dest->length;
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		o_addchr(dest, 0x80 | '`');
+		if (!add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"'))
+			return 0; /* error */
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+		//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+		goto again;
+	}
+#endif
+	o_addQchr(dest, ch);
+	if (ch == SPECIAL_VAR_SYMBOL) {
+		/* Convert "^C" to corresponding special variable reference */
+		o_addchr(dest, SPECIAL_VAR_QUOTED_SVS);
+		o_addchr(dest, SPECIAL_VAR_SYMBOL);
+	}
+	goto again;
+#undef as_string
+}
+
+/*
+ * Scan input until EOF or end_trigger char.
+ * Return a list of pipes to execute, or NULL on EOF
+ * or if end_trigger character is met.
+ * On syntax error, exit if shell is not interactive,
+ * reset parsing machinery and start parsing anew,
+ * or return ERR_PTR.
+ */
+static struct pipe *parse_stream(char **pstring,
+		int *heredoc_cnt_ptr,
+		struct in_str *input,
+		int end_trigger)
+{
+	struct parse_context ctx;
+	int heredoc_cnt;
+
+	/* Single-quote triggers a bypass of the main loop until its mate is
+	 * found.  When recursing, quote state is passed in via ctx.word.o_expflags.
+	 */
+	debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
+			end_trigger ? end_trigger : 'X');
+	debug_enter();
+
+	initialize_context(&ctx);
+
+	/* If very first arg is "" or '', ctx.word.data may end up NULL.
+	 * Preventing this:
+	 */
+	ctx.word.data = xzalloc(1); /* start as "", not as NULL */
+
+	/* We used to separate words on $IFS here. This was wrong.
+	 * $IFS is used only for word splitting when $var is expanded,
+	 * here we should use blank chars as separators, not $IFS
+	 */
+
+	heredoc_cnt = 0;
+	while (1) {
+		const char *is_blank;
+		const char *is_special;
+		int ch;
+		int next;
+		int redir_fd;
+		redir_type redir_style;
+
+		ch = i_getch(input);
+		debug_printf_parse(": ch=%c (%d) escape=%d\n",
+				ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+		if (ch == EOF) {
+			struct pipe *pi;
+
+			if (heredoc_cnt) {
+				syntax_error_unterm_str("here document");
+				goto parse_error_exitcode1;
+			}
+			if (end_trigger == ')') {
+				syntax_error_unterm_ch('(');
+				goto parse_error_exitcode1;
+			}
+			if (end_trigger == '}') {
+				syntax_error_unterm_ch('{');
+				goto parse_error_exitcode1;
+			}
+
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			o_free_and_set_NULL(&ctx.word);
+			done_pipe(&ctx, PIPE_SEQ);
+			pi = ctx.list_head;
+			/* If we got nothing... */
+			/* (this makes bare "&" cmd a no-op.
+			 * bash says: "syntax error near unexpected token '&'") */
+			if (pi->num_cmds == 0
+			IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE)
+			) {
+				free_pipe_list(pi);
+				pi = NULL;
+			}
+#if !BB_MMU
+			debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data);
+			if (pstring)
+				*pstring = ctx.as_string.data;
+			else
+				o_free(&ctx.as_string);
+#endif
+			// heredoc_cnt must be 0 here anyway
+			//if (heredoc_cnt_ptr)
+			//	*heredoc_cnt_ptr = heredoc_cnt;
+			debug_leave();
+			debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt);
+			debug_printf_parse("parse_stream return %p\n", pi);
+			return pi;
+		}
+
+		/* Handle "'" and "\" first, as they won't play nice with
+		 * i_peek_and_eat_bkslash_nl() anyway:
+		 *   echo z\\
+		 * and
+		 *   echo '\
+		 *   '
+		 * would break.
+		 */
+		if (ch == '\\') {
+			ch = i_getch(input);
+			if (ch == '\n')
+				continue; /* drop \<newline>, get next char */
+			nommu_addchr(&ctx.as_string, '\\');
+			if (ch == SPECIAL_VAR_SYMBOL) {
+				nommu_addchr(&ctx.as_string, ch);
+				/* Convert \^C to corresponding special variable reference */
+				goto case_SPECIAL_VAR_SYMBOL;
+			}
+			o_addchr(&ctx.word, '\\');
+			if (ch == EOF) {
+				/* Testcase: eval 'echo Ok\' */
+				/* bash-4.3.43 was removing backslash,
+				 * but 4.4.19 retains it, most other shells too
+				 */
+				continue; /* get next char */
+			}
+			/* Example: echo Hello \2>file
+			 * we need to know that word 2 is quoted
+			 */
+			ctx.word.has_quoted_part = 1;
+			nommu_addchr(&ctx.as_string, ch);
+			o_addchr(&ctx.word, ch);
+			continue; /* get next char */
+		}
+		nommu_addchr(&ctx.as_string, ch);
+		if (ch == '\'') {
+			ctx.word.has_quoted_part = 1;
+			next = i_getch(input);
+			if (next == '\'' && !ctx.pending_redirect)
+				goto insert_empty_quoted_str_marker;
+
+			ch = next;
+			while (1) {
+				if (ch == EOF) {
+					syntax_error_unterm_ch('\'');
+					goto parse_error_exitcode1;
+				}
+				nommu_addchr(&ctx.as_string, ch);
+				if (ch == '\'')
+					break;
+				if (ch == SPECIAL_VAR_SYMBOL) {
+					/* Convert raw ^C to corresponding special variable reference */
+					o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+					o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS);
+				}
+				o_addqchr(&ctx.word, ch);
+				ch = i_getch(input);
+			}
+			continue; /* get next char */
+		}
+
+		next = '\0';
+		if (ch != '\n')
+			next = i_peek_and_eat_bkslash_nl(input);
+
+		is_special = "{}<>&|();#" /* special outside of "str" */
+				"$\"" IF_HUSH_TICK("`") /* always special */
+				SPECIAL_VAR_SYMBOL_STR;
+#if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+		if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) {
+			/* In [[ ]], {}<>&|() are not special */
+			is_special += 8;
+		} else
+#endif
+		/* Are { and } special here? */
+		if (ctx.command->argv /* word [word]{... - non-special */
+		 || ctx.word.length       /* word{... - non-special */
+		 || ctx.word.has_quoted_part     /* ""{... - non-special */
+		 || (next != ';'             /* }; - special */
+		    && next != ')'           /* }) - special */
+		    && next != '('           /* {( - special */
+		    && next != '&'           /* }& and }&& ... - special */
+		    && next != '|'           /* }|| ... - special */
+		    && !strchr(defifs, next) /* {word - non-special */
+		    )
+		) {
+			/* They are not special, skip "{}" */
+			is_special += 2;
+		}
+		is_special = strchr(is_special, ch);
+		is_blank = strchr(defifs, ch);
+
+		if (!is_special && !is_blank) { /* ordinary char */
+ ordinary_char:
+			o_addQchr(&ctx.word, ch);
+			if ((ctx.is_assignment == MAYBE_ASSIGNMENT
+			    || ctx.is_assignment == WORD_IS_KEYWORD)
+			 && ch == '='
+			 && endofname(ctx.word.data)[0] == '='
+			) {
+				ctx.is_assignment = DEFINITELY_ASSIGNMENT;
+				debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
+			}
+			continue;
+		}
+
+		if (is_blank) {
+#if ENABLE_HUSH_LINENO_VAR
+/* Case:
+ * "while ...; do<whitespace><newline>
+ *	cmd ..."
+ * would think that "cmd" starts in <whitespace> -
+ * i.e., at the previous line.
+ * We need to skip all whitespace before newlines.
+ */
+			while (ch != '\n') {
+				next = i_peek(input);
+				if (next != ' ' && next != '\t' && next != '\n')
+					break; /* next char is not ws */
+				ch = i_getch(input);
+			}
+			/* ch == last eaten whitespace char */
+#endif
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			if (ch == '\n') {
+				/* Is this a case when newline is simply ignored?
+				 * Some examples:
+				 * "cmd | <newline> cmd ..."
+				 * "case ... in <newline> word) ..."
+				 */
+				if (IS_NULL_CMD(ctx.command)
+				 && ctx.word.length == 0
+				 && !ctx.word.has_quoted_part
+				 && heredoc_cnt == 0
+				) {
+					/* This newline can be ignored. But...
+					 * Without check #1, interactive shell
+					 * ignores even bare <newline>,
+					 * and shows the continuation prompt:
+					 * ps1_prompt$ <enter>
+					 * ps2> _   <=== wrong, should be ps1
+					 * Without check #2, "cmd & <newline>"
+					 * is similarly mistreated.
+					 * (BTW, this makes "cmd & cmd"
+					 * and "cmd && cmd" non-orthogonal.
+					 * Really, ask yourself, why
+					 * "cmd && <newline>" doesn't start
+					 * cmd but waits for more input?
+					 * 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 */
+					 && pi->followup != PIPE_BG /* check #2 */
+					) {
+						continue;
+					}
+				}
+				/* Treat newline as a command separator. */
+				done_pipe(&ctx, PIPE_SEQ);
+				debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt);
+				if (heredoc_cnt) {
+					heredoc_cnt = fetch_heredocs(&ctx.as_string, ctx.list_head, heredoc_cnt, input);
+					if (heredoc_cnt != 0)
+						goto parse_error_exitcode1;
+				}
+				ctx.is_assignment = MAYBE_ASSIGNMENT;
+				debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
+				ch = ';';
+				/* note: if (is_blank) continue;
+				 * will still trigger for us */
+			}
+		}
+
+		/* "cmd}" or "cmd }..." without semicolon or &:
+		 * } is an ordinary char in this case, even inside { cmd; }
+		 * Pathological example: { ""}; } should exec "}" cmd
+		 */
+		if (ch == '}') {
+			if (ctx.word.length != 0 /* word} */
+			 || ctx.word.has_quoted_part    /* ""} */
+			) {
+				goto ordinary_char;
+			}
+			if (!IS_NULL_CMD(ctx.command)) { /* cmd } */
+				/* Generally, there should be semicolon: "cmd; }"
+				 * However, bash allows to omit it if "cmd" is
+				 * a group. Examples:
+				 * { { echo 1; } }
+				 * {(echo 1)}
+				 * { echo 0 >&2 | { echo 1; } }
+				 * { while false; do :; done }
+				 * { case a in b) ;; esac }
+				 */
+				if (ctx.command->group)
+					goto term_group;
+				goto ordinary_char;
+			}
+			if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */
+				/* Can't be an end of {cmd}, skip the check */
+				goto skip_end_trigger;
+			/* else: } does terminate a group */
+		}
+ term_group:
+		if (end_trigger && end_trigger == ch
+		 && (ch != ';' || heredoc_cnt == 0)
+#if ENABLE_HUSH_CASE
+		 && (ch != ')'
+		    || ctx.ctx_res_w != RES_MATCH
+		    || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0)
+		    )
+#endif
+		) {
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			done_pipe(&ctx, PIPE_SEQ);
+			ctx.is_assignment = MAYBE_ASSIGNMENT;
+			debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
+			/* Do we sit outside of any if's, loops or case's? */
+			if (!HAS_KEYWORDS
+			IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+			) {
+				o_free_and_set_NULL(&ctx.word);
+#if !BB_MMU
+				debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data);
+				if (pstring)
+					*pstring = ctx.as_string.data;
+				else
+					o_free(&ctx.as_string);
+#endif
+				if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) {
+					/* Example: bare "{ }", "()" */
+					G.last_exitcode = 2; /* bash compat */
+					syntax_error_unexpected_ch(ch);
+					goto parse_error;
+				}
+				if (heredoc_cnt_ptr)
+					*heredoc_cnt_ptr = heredoc_cnt;
+				debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt);
+				debug_printf_parse("parse_stream return %p: "
+						"end_trigger char found\n",
+						ctx.list_head);
+				debug_leave();
+				return ctx.list_head;
+			}
+		}
+
+		if (is_blank)
+			continue;
+
+		/* Catch <, > before deciding whether this word is
+		 * an assignment. a=1 2>z b=2: b=2 is still assignment */
+		switch (ch) {
+		case '>':
+			redir_fd = redirect_opt_num(&ctx.word);
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			redir_style = REDIRECT_OVERWRITE;
+			if (next == '>') {
+				redir_style = REDIRECT_APPEND;
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			}
+#if 0
+			else if (next == '(') {
+				syntax_error(">(process) not supported");
+				goto parse_error_exitcode1;
+			}
+#endif
+			if (parse_redirect(&ctx, redir_fd, redir_style, input))
+				goto parse_error_exitcode1;
+			continue; /* get next char */
+		case '<':
+			redir_fd = redirect_opt_num(&ctx.word);
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			redir_style = REDIRECT_INPUT;
+			if (next == '<') {
+				redir_style = REDIRECT_HEREDOC;
+				heredoc_cnt++;
+				debug_printf_heredoc("++heredoc_cnt=%d\n", heredoc_cnt);
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			} else if (next == '>') {
+				redir_style = REDIRECT_IO;
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+			}
+#if 0
+			else if (next == '(') {
+				syntax_error("<(process) not supported");
+				goto parse_error_exitcode1;
+			}
+#endif
+			if (parse_redirect(&ctx, redir_fd, redir_style, input))
+				goto parse_error_exitcode1;
+			continue; /* get next char */
+		case '#':
+			if (ctx.word.length == 0 && !ctx.word.has_quoted_part) {
+				/* skip "#comment" */
+				/* note: we do not add it to &ctx.as_string */
+/* TODO: in bash:
+ * comment inside $() goes to the next \n, even inside quoted string (!):
+ * cmd "$(cmd2 #comment)" - syntax error
+ * cmd "`cmd2 #comment`" - ok
+ * We accept both (comment ends where command subst ends, in both cases).
+ */
+				while (1) {
+					ch = i_peek(input);
+					if (ch == '\n') {
+						nommu_addchr(&ctx.as_string, '\n');
+						break;
+					}
+					ch = i_getch(input);
+					if (ch == EOF)
+						break;
+				}
+				continue; /* get next char */
+			}
+			break;
+		}
+ skip_end_trigger:
+
+		if (ctx.is_assignment == MAYBE_ASSIGNMENT
+		 /* check that we are not in word in "a=1 2>word b=1": */
+		 && !ctx.pending_redirect
+		) {
+			/* ch is a special char and thus this word
+			 * cannot be an assignment */
+			ctx.is_assignment = NOT_ASSIGNMENT;
+			debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
+		}
+
+		/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
+
+		switch (ch) {
+		case_SPECIAL_VAR_SYMBOL:
+		case SPECIAL_VAR_SYMBOL:
+			/* Convert raw ^C to corresponding special variable reference */
+			o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+			o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS);
+			/* fall through */
+		case '#':
+			/* non-comment #: "echo a#b" etc */
+			o_addchr(&ctx.word, ch);
+			continue; /* get next char */
+		case '$':
+			if (parse_dollar_squote(&ctx.as_string, &ctx.word, input))
+				continue; /* get next char */
+			if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) {
+				debug_printf_parse("parse_stream parse error: "
+					"parse_dollar returned 0 (error)\n");
+				goto parse_error_exitcode1;
+			}
+			continue; /* get next char */
+		case '"':
+			ctx.word.has_quoted_part = 1;
+			if (next == '"' && !ctx.pending_redirect) {
+				i_getch(input); /* eat second " */
+ insert_empty_quoted_str_marker:
+				nommu_addchr(&ctx.as_string, next);
+				o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+				o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+				continue; /* get next char */
+			}
+			if (ctx.is_assignment == NOT_ASSIGNMENT)
+				ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
+			if (!encode_string(&ctx.as_string, &ctx.word, input, '"'))
+				goto parse_error_exitcode1;
+			ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
+			continue; /* get next char */
+#if ENABLE_HUSH_TICK
+		case '`': {
+			USE_FOR_NOMMU(unsigned pos;)
+
+			o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+			o_addchr(&ctx.word, '`');
+			USE_FOR_NOMMU(pos = ctx.word.length;)
+			if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0))
+				goto parse_error_exitcode1;
+# if !BB_MMU
+			o_addstr(&ctx.as_string, ctx.word.data + pos);
+			o_addchr(&ctx.as_string, '`');
+# endif
+			o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+			//debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos);
+			continue; /* get next char */
+		}
+#endif
+		case ';':
+#if ENABLE_HUSH_CASE
+ case_semi:
+#endif
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			done_pipe(&ctx, PIPE_SEQ);
+#if ENABLE_HUSH_CASE
+			/* Eat multiple semicolons, detect
+			 * whether it means something special */
+			while (1) {
+				ch = i_peek_and_eat_bkslash_nl(input);
+				if (ch != ';')
+					break;
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+				if (ctx.ctx_res_w == RES_CASE_BODY) {
+					ctx.ctx_dsemicolon = 1;
+					ctx.ctx_res_w = RES_MATCH;
+					break;
+				}
+			}
+#endif
+ new_cmd:
+			/* We just finished a cmd. New one may start
+			 * with an assignment */
+			ctx.is_assignment = MAYBE_ASSIGNMENT;
+			debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
+			continue; /* get next char */
+		case '&':
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+			if (next == '&') {
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+				done_pipe(&ctx, PIPE_AND);
+			} else {
+				done_pipe(&ctx, PIPE_BG);
+			}
+			goto new_cmd;
+		case '|':
+			if (done_word(&ctx)) {
+				goto parse_error_exitcode1;
+			}
+#if ENABLE_HUSH_CASE
+			if (ctx.ctx_res_w == RES_MATCH)
+				break; /* we are in case's "word | word)" */
+#endif
+			if (next == '|') { /* || */
+				ch = i_getch(input);
+				nommu_addchr(&ctx.as_string, ch);
+				done_pipe(&ctx, PIPE_OR);
+			} else {
+				/* we could pick up a file descriptor choice here
+				 * with redirect_opt_num(), but bash doesn't do it.
+				 * "echo foo 2| cat" yields "foo 2". */
+				done_command(&ctx);
+			}
+			goto new_cmd;
+		case '(':
+#if ENABLE_HUSH_CASE
+			/* "case... in [(]word)..." - skip '(' */
+			if (ctx.ctx_res_w == RES_MATCH
+			 && ctx.command->argv == NULL /* not (word|(... */
+			 && ctx.word.length == 0 /* not word(... */
+			 && ctx.word.has_quoted_part == 0 /* not ""(... */
+			) {
+				continue; /* get next char */
+			}
+#endif
+			/* fall through */
+		case '{': {
+			int n = parse_group(&ctx, input, ch);
+			if (n < 0) {
+				goto parse_error_exitcode1;
+			}
+			debug_printf_heredoc("parse_group done, needs heredocs:%d\n", n);
+			heredoc_cnt += n;
+			goto new_cmd;
+		}
+		case ')':
+#if ENABLE_HUSH_CASE
+			if (ctx.ctx_res_w == RES_MATCH)
+				goto case_semi;
+#endif
+		case '}':
+			/* proper use of this character is caught by end_trigger:
+			 * if we see {, we call parse_group(..., end_trigger='}')
+			 * and it will match } earlier (not here). */
+			G.last_exitcode = 2;
+			syntax_error_unexpected_ch(ch);
+			goto parse_error;
+		default:
+			if (HUSH_DEBUG)
+				bb_error_msg_and_die("BUG: unexpected %c", ch);
+		}
+	} /* while (1) */
+
+ parse_error_exitcode1:
+	G.last_exitcode = 1;
+ parse_error:
+	{
+		struct parse_context *pctx;
+		IF_HAS_KEYWORDS(struct parse_context *p2;)
+
+		/* Clean up allocated tree.
+		 * Sample for finding leaks on syntax error recovery path.
+		 * Run it from interactive shell, watch pmap `pidof hush`.
+		 * while if false; then false; fi; do break; fi
+		 * Samples to catch leaks at execution:
+		 * while if (true | { true;}); then echo ok; fi; do break; done
+		 * while if (true | { true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done
+		 */
+		pctx = &ctx;
+		do {
+			/* Update pipe/command counts,
+			 * otherwise freeing may miss some */
+			done_pipe(pctx, PIPE_SEQ);
+			debug_printf_clean("freeing list %p from ctx %p\n",
+					pctx->list_head, pctx);
+			debug_print_tree(pctx->list_head, 0);
+			free_pipe_list(pctx->list_head);
+			debug_printf_clean("freed list %p\n", pctx->list_head);
+#if !BB_MMU
+			o_free(&pctx->as_string);
+#endif
+			IF_HAS_KEYWORDS(p2 = pctx->stack;)
+			if (pctx != &ctx) {
+				free(pctx);
+			}
+			IF_HAS_KEYWORDS(pctx = p2;)
+		} while (HAS_KEYWORDS && pctx);
+
+		o_free(&ctx.word);
+#if !BB_MMU
+		if (pstring)
+			*pstring = NULL;
+#endif
+		debug_leave();
+		return ERR_PTR;
+	}
+}
+
+
+/*** Execution routines ***/
+
+/* Expansion can recurse, need forward decls: */
+#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
+#define expand_string_to_string(str, EXP_flags, do_unbackslash) \
+	expand_string_to_string(str)
+#endif
+static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash);
+#if ENABLE_HUSH_TICK
+static int process_command_subs(o_string *dest, const char *s);
+#endif
+static int expand_vars_to_list(o_string *output, int n, char *arg);
+
+/* expand_strvec_to_strvec() takes a list of strings, expands
+ * all variable references within and returns a pointer to
+ * a list of expanded strings, possibly with larger number
+ * of strings. (Think VAR="a b"; echo $VAR).
+ * This new list is allocated as a single malloc block.
+ * NULL-terminated list of char* pointers is at the beginning of it,
+ * followed by strings themselves.
+ * Caller can deallocate entire list by single free(list). */
+
+/* A horde of its helpers come first: */
+
+static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
+{
+	while (--len >= 0) {
+		char c = *str++;
+
+#if ENABLE_HUSH_BRACE_EXPANSION
+		if (c == '{' || c == '}') {
+			/* { -> \{, } -> \} */
+			o_addchr(o, '\\');
+			/* And now we want to add { or } and continue:
+			 *  o_addchr(o, c);
+			 *  continue;
+			 * luckily, just falling through achieves this.
+			 */
+		}
+#endif
+		o_addchr(o, c);
+		if (c == '\\') {
+			/* \z -> \\\z; \<eol> -> \\<eol> */
+			o_addchr(o, '\\');
+			if (len) {
+				len--;
+				o_addchr(o, '\\');
+				o_addchr(o, *str++);
+			}
+		}
+	}
+}
+
+/* Store given string, finalizing the word and starting new one whenever
+ * we encounter IFS char(s). This is used for expanding variable values.
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * Return in output->ended_in_ifs:
+ * 1 - ended with IFS char, else 0 (this includes case of empty str).
+ */
+static int expand_on_ifs(o_string *output, int n, const char *str)
+{
+	int last_is_ifs = 0;
+
+	while (1) {
+		int word_len;
+
+		if (!*str)  /* EOL - do not finalize word */
+			break;
+		word_len = strcspn(str, G.ifs);
+		if (word_len) {
+			/* We have WORD_LEN leading non-IFS chars */
+			if (!(output->o_expflags & EXP_FLAG_GLOB)) {
+				o_addblock(output, str, word_len);
+			} else {
+				/* Protect backslashes against globbing up :)
+				 * Example: "v='\*'; echo b$v" prints "b\*"
+				 * (and does not try to glob on "*")
+				 */
+				o_addblock_duplicate_backslash(output, str, word_len);
+				/*/ Why can't we do it easier? */
+				/*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
+				/*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
+			}
+			last_is_ifs = 0;
+			str += word_len;
+			if (!*str)  /* EOL - do not finalize word */
+				break;
+		}
+
+		/* We know str here points to at least one IFS char */
+		last_is_ifs = 1;
+		str += strspn(str, G.ifs_whitespace); /* skip IFS whitespace chars */
+		if (!*str)  /* EOL - do not finalize word */
+			break;
+
+		if (G.ifs_whitespace != G.ifs /* usually false ($IFS is usually all whitespace), */
+		 && strchr(G.ifs, *str)       /* the second check would fail */
+		) {
+			/* This is a non-whitespace $IFS char */
+			/* Skip it and IFS whitespace chars, start new word */
+			str++;
+			str += strspn(str, G.ifs_whitespace);
+			goto new_word;
+		}
+
+		/* Start new word... but not always! */
+		/* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
+		if (output->has_quoted_part
+		/*
+		 * Case "v=' a'; echo $v":
+		 * here nothing precedes the space in $v expansion,
+		 * therefore we should not finish the word
+		 * (IOW: if there *is* word to finalize, only then do it):
+		 * It's okay if this accesses the byte before first argv[]:
+		 * past call to o_save_ptr() cleared it to zero byte
+		 * (grep for -prev-ifs-check-).
+		 */
+		 || output->data[output->length - 1]
+		) {
+ new_word:
+			o_addchr(output, '\0');
+			debug_print_list("expand_on_ifs", output, n);
+			n = o_save_ptr(output, n);
+		}
+	}
+
+	output->ended_in_ifs = last_is_ifs;
+	debug_print_list("expand_on_ifs[1]", output, n);
+	return n;
+}
+
+/* Helper to expand $((...)) and heredoc body. These act as if
+ * they are in double quotes, with the exception that they are not :).
+ * Just the rules are similar: "expand only $var and `cmd`"
+ *
+ * Returns malloced string.
+ * As an optimization, we return NULL if expansion is not needed.
+ */
+static char *encode_then_expand_string(const char *str)
+{
+	char *exp_str;
+	struct in_str input;
+	o_string dest = NULL_O_STRING;
+	const char *cp;
+
+	cp = str;
+	for (;;) {
+		if (!*cp) return NULL; /* string has no special chars */
+		if (*cp == '$') break;
+		if (*cp == '\\') break;
+#if ENABLE_HUSH_TICK
+		if (*cp == '`') break;
+#endif
+		cp++;
+	}
+
+	/* We need to expand. Example:
+	 * echo $(($a + `echo 1`)) $((1 + $((2)) ))
+	 */
+	setup_string_in_str(&input, str);
+	encode_string(NULL, &dest, &input, EOF);
+//TODO: error check (encode_string returns 0 on error)?
+	//bb_error_msg("'%s' -> '%s'", str, dest.data);
+	exp_str = expand_string_to_string(dest.data,
+			EXP_FLAG_ESC_GLOB_CHARS,
+			/*unbackslash:*/ 1
+	);
+	//bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+	o_free(&dest);
+	return exp_str;
+}
+
+static const char *first_special_char_in_vararg(const char *cp)
+{
+	for (;;) {
+		if (!*cp) return NULL; /* string has no special chars */
+		if (*cp == '$') return cp;
+		if (*cp == '\\') return cp;
+		if (*cp == '\'') return cp;
+		if (*cp == '"') return cp;
+#if ENABLE_HUSH_TICK
+		if (*cp == '`') return cp;
+#endif
+		/* dquoted "${x:+ARG}" should not glob, therefore
+		 * '*' et al require some non-literal processing: */
+		if (*cp == '*') return cp;
+		if (*cp == '?') return cp;
+		if (*cp == '[') return cp;
+		cp++;
+	}
+}
+
+/* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}.
+ * These can contain single- and double-quoted strings,
+ * and treated as if the ARG string is initially unquoted. IOW:
+ * ${var#ARG} and "${var#ARG}" treat ARG the same (ARG can even be
+ * a dquoted string: "${var#"zz"}"), the difference only comes later
+ * (word splitting and globbing of the ${var...} result).
+ */
+#if !BASH_PATTERN_SUBST
+#define encode_then_expand_vararg(str, handle_squotes, do_unbackslash) \
+	encode_then_expand_vararg(str, handle_squotes)
+#endif
+static char *encode_then_expand_vararg(const char *str, int handle_squotes, int do_unbackslash)
+{
+#if !BASH_PATTERN_SUBST && ENABLE_HUSH_CASE
+	const int do_unbackslash = 0;
+#endif
+	char *exp_str;
+	struct in_str input;
+	o_string dest = NULL_O_STRING;
+
+	if (!first_special_char_in_vararg(str)) {
+		/* string has no special chars */
+		return NULL;
+	}
+
+	setup_string_in_str(&input, str);
+	dest.data = xzalloc(1); /* start as "", not as NULL */
+	exp_str = NULL;
+
+	for (;;) {
+		int ch;
+
+		ch = i_getch(&input);
+		debug_printf_parse("%s: ch=%c (%d) escape=%d\n",
+				__func__, ch, ch, !!dest.o_expflags);
+
+		if (!dest.o_expflags) {
+			if (ch == EOF)
+				break;
+			if (handle_squotes && ch == '\'') {
+				if (!add_till_single_quote_dquoted(&dest, &input))
+					goto ret; /* error */
+				continue;
+			}
+		}
+		if (ch == EOF) {
+			syntax_error_unterm_ch('"');
+			goto ret; /* error */
+		}
+		if (ch == '"') {
+			dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS;
+			continue;
+		}
+		if (ch == '\\') {
+			ch = i_getch(&input);
+			if (ch == EOF) {
+//example? error message?	syntax_error_unterm_ch('"');
+				debug_printf_parse("%s: error: \\<eof>\n", __func__);
+				goto ret;
+			}
+			o_addqchr(&dest, ch);
+			continue;
+		}
+		if (ch == '$') {
+			if (parse_dollar_squote(NULL, &dest, &input))
+				continue;
+			if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) {
+				debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
+				goto ret;
+			}
+			continue;
+		}
+#if ENABLE_HUSH_TICK
+		if (ch == '`') {
+			//unsigned pos = dest->length;
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			o_addchr(&dest, 0x80 | '`');
+			if (!add_till_backquote(&dest, &input,
+					/*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */
+				)
+			) {
+				goto ret; /* error */
+			}
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+			continue;
+		}
+#endif
+		o_addQchr(&dest, ch);
+	} /* for (;;) */
+
+	debug_printf_parse("encode: '%s' -> '%s'\n", str, dest.data);
+	exp_str = expand_string_to_string(dest.data,
+			do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0,
+			do_unbackslash
+	);
+ ret:
+	debug_printf_parse("expand: '%s' -> '%s'\n", dest.data, exp_str);
+	o_free(&dest);
+	return exp_str;
+}
+
+/* Expanding ARG in ${var+ARG}, ${var-ARG}
+ */
+static int encode_then_append_var_plusminus(o_string *output, int n,
+		char *str, int dquoted)
+{
+	struct in_str input;
+	o_string dest = NULL_O_STRING;
+
+	if (!first_special_char_in_vararg(str)
+	 && '\0' == str[strcspn(str, G.ifs)]
+	) {
+		/* string has no special chars
+		 * && string has no $IFS chars
+		 */
+		if (dquoted) {
+			/* Prints 1 (quoted expansion is a "" word, not nothing):
+			 * set -- "${notexist-}"; echo $#
+			 */
+			output->has_quoted_part = 1;
+		}
+		return expand_vars_to_list(output, n, str);
+	}
+
+	setup_string_in_str(&input, str);
+
+	for (;;) {
+		int ch;
+
+		ch = i_getch(&input);
+		debug_printf_parse("%s: ch=%c (%d) escape=%x\n",
+				__func__, ch, ch, dest.o_expflags);
+
+		if (!dest.o_expflags) {
+			if (ch == EOF)
+				break;
+			if (!dquoted && strchr(G.ifs, ch)) {
+				/* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word.
+				 * do not assume we are at the start of the word (PREFIX above).
+				 */
+				if (dest.data) {
+					n = expand_vars_to_list(output, n, dest.data);
+					o_free_and_set_NULL(&dest);
+					o_addchr(output, '\0');
+					n = o_save_ptr(output, n); /* create next word */
+				} else
+				if (output->length != o_get_last_ptr(output, n)
+				 || output->has_quoted_part
+				) {
+					/* For these cases:
+					 * f() { for i; do echo "|$i|"; done; }; x=x
+					 * f a${x:+ }b  # 1st condition
+					 * |a|
+					 * |b|
+					 * f ""${x:+ }b  # 2nd condition
+					 * ||
+					 * |b|
+					 */
+					o_addchr(output, '\0');
+					n = o_save_ptr(output, n); /* create next word */
+				}
+				continue;
+			}
+			if (!dquoted && ch == '\'') {
+				if (!add_till_single_quote_dquoted(&dest, &input))
+					goto ret; /* error */
+				o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+				o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+				continue;
+			}
+		}
+		if (ch == EOF) {
+			syntax_error_unterm_ch('"');
+			goto ret; /* error */
+		}
+		if (ch == '"') {
+			dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS;
+			if (dest.o_expflags) {
+				o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+				o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			}
+			continue;
+		}
+		if (ch == '\\') {
+			ch = i_getch(&input);
+			if (ch == EOF) {
+//example? error message?	syntax_error_unterm_ch('"');
+				debug_printf_parse("%s: error: \\<eof>\n", __func__);
+				goto ret;
+			}
+			o_addqchr(&dest, ch);
+			continue;
+		}
+		if (ch == '$') {
+			if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ (dest.o_expflags || dquoted) ? 0x80 : 0)) {
+				debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
+				goto ret;
+			}
+			continue;
+		}
+#if ENABLE_HUSH_TICK
+		if (ch == '`') {
+			//unsigned pos = dest->length;
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`');
+			if (!add_till_backquote(&dest, &input,
+					/*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */
+				)
+			) {
+				goto ret; /* error */
+			}
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+			continue;
+		}
+#endif
+		if (dquoted) {
+			/* Always glob-protect if in dquotes:
+			 * x=x; echo "${x:+/bin/c*}" - prints: /bin/c*
+			 * x=x; echo "${x:+"/bin/c*"}" - prints: /bin/c*
+			 */
+			o_addqchr(&dest, ch);
+		} else {
+			/* Glob-protect only if char is quoted:
+			 * x=x; echo ${x:+/bin/c*} - prints many filenames
+			 * x=x; echo ${x:+"/bin/c*"} - prints: /bin/c*
+			 */
+			o_addQchr(&dest, ch);
+		}
+	} /* for (;;) */
+
+	if (dest.data) {
+		n = expand_vars_to_list(output, n, dest.data);
+	}
+ ret:
+	o_free(&dest);
+	return n;
+}
+
+#if ENABLE_FEATURE_SH_MATH
+static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
+{
+	arith_state_t math_state;
+	arith_t res;
+	char *exp_str;
+
+	math_state.lookupvar = get_local_var_value;
+	math_state.setvar = set_local_var_from_halves;
+	//math_state.endofname = endofname;
+	exp_str = encode_then_expand_string(arg);
+	res = arith(&math_state, exp_str ? exp_str : arg);
+	free(exp_str);
+	if (errmsg_p)
+		*errmsg_p = math_state.errmsg;
+	if (math_state.errmsg)
+		msg_and_die_if_script(math_state.errmsg);
+	return res;
+}
+#endif
+
+#if BASH_PATTERN_SUBST
+/* ${var/[/]pattern[/repl]} helpers */
+static char *strstr_pattern(char *val, const char *pattern, int *size)
+{
+	int sz = strcspn(pattern, "*?[\\");
+	if (pattern[sz] == '\0') {
+		/* Optimization for trivial patterns.
+		 * Testcase for very slow replace (performs about 22k replaces):
+		 * x=::::::::::::::::::::::
+		 * x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;echo ${#x}
+		 * echo "${x//:/|}"
+		 */
+		*size = sz;
+		return strstr(val, pattern);
+	}
+
+	while (1) {
+		char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF);
+		debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end);
+		if (end) {
+			*size = end - val;
+			return val;
+		}
+		if (*val == '\0')
+			return NULL;
+		/* Optimization: if "*pat" did not match the start of "string",
+		 * we know that "tring", "ring" etc will not match too:
+		 */
+		if (pattern[0] == '*')
+			return NULL;
+		val++;
+	}
+}
+static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op)
+{
+	char *result = NULL;
+	unsigned res_len = 0;
+	unsigned repl_len = strlen(repl);
+
+	/* Null pattern never matches, including if "var" is empty */
+	if (!pattern[0])
+		return result; /* NULL, no replaces happened */
+
+	while (1) {
+		int size;
+		char *s = strstr_pattern(val, pattern, &size);
+		if (!s)
+			break;
+
+		result = xrealloc(result, res_len + (s - val) + repl_len + 1);
+		strcpy(mempcpy(result + res_len, val, s - val), repl);
+		res_len += (s - val) + repl_len;
+		debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result);
+
+		val = s + size;
+		if (exp_op == '/')
+			break;
+	}
+	if (*val && result) {
+		result = xrealloc(result, res_len + strlen(val) + 1);
+		strcpy(result + res_len, val);
+		debug_printf_varexp("val:'%s' result:'%s'\n", val, result);
+	}
+	debug_printf_varexp("result:'%s'\n", result);
+	return result;
+}
+#endif /* BASH_PATTERN_SUBST */
+
+static int append_str_maybe_ifs_split(o_string *output, int n,
+		int first_ch, const char *val)
+{
+	if (!(first_ch & 0x80)) { /* unquoted $VAR */
+		debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
+				!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+		if (val && val[0])
+			n = expand_on_ifs(output, n, val);
+	} else { /* quoted "$VAR" */
+		output->has_quoted_part = 1;
+		debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
+				!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+		if (val && val[0])
+			o_addQstr(output, val);
+	}
+	return n;
+}
+
+/* Handle <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct.
+ */
+static NOINLINE int expand_one_var(o_string *output, int n,
+		int first_ch, char *arg, char **pp)
+{
+	const char *val;
+	char *to_be_freed;
+	char *p;
+	char *var;
+	char exp_op;
+	char exp_save = exp_save; /* for compiler */
+	char *exp_saveptr; /* points to expansion operator */
+	char *exp_word = exp_word; /* for compiler */
+	char arg0;
+
+	val = NULL;
+	to_be_freed = NULL;
+	p = *pp;
+	*p = '\0'; /* replace trailing SPECIAL_VAR_SYMBOL */
+	var = arg;
+	exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
+	arg0 = arg[0];
+	arg[0] = (arg0 & 0x7f);
+	exp_op = 0;
+
+	if (arg[0] == '#' && arg[1] /* ${#...} but not ${#} */
+	 && (!exp_saveptr               /* and ( not(${#<op_char>...}) */
+	    || (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */
+	    )		/* NB: skipping ^^^specvar check mishandles ${#::2} */
+	) {
+		/* It must be length operator: ${#var} */
+		var++;
+		exp_op = 'L';
+	} else {
+		/* Maybe handle parameter expansion */
+		if (exp_saveptr /* if 2nd char is one of expansion operators */
+		 && strchr(NUMERIC_SPECVARS_STR, arg[0]) /* 1st char is special variable */
+		) {
+			/* ${?:0}, ${#[:]%0} etc */
+			exp_saveptr = var + 1;
+		} else {
+			/* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
+			exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
+		}
+		exp_op = exp_save = *exp_saveptr;
+		if (exp_op) {
+			exp_word = exp_saveptr + 1;
+			if (exp_op == ':') {
+				exp_op = *exp_word++;
+//TODO: try ${var:} and ${var:bogus} in non-bash config
+				if (BASH_SUBSTR
+				 && (!exp_op || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
+				) {
+					/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
+					exp_op = ':';
+					exp_word--;
+				}
+			}
+			*exp_saveptr = '\0';
+		} /* else: it's not an expansion op, but bare ${var} */
+	}
+
+	/* Look up the variable in question */
+	if (isdigit(var[0])) {
+		/* parse_dollar should have vetted var for us */
+		int nn = xatoi_positive(var);
+		if (nn < G.global_argc)
+			val = G.global_argv[nn];
+		/* else val remains NULL: $N with too big N */
+	} else {
+		switch (var[0]) {
+		case '$': /* pid */
+			val = utoa(G.root_pid);
+			break;
+		case '!': /* bg pid */
+			val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
+			break;
+		case '?': /* exitcode */
+			val = utoa(G.last_exitcode);
+			break;
+		case '#': /* argc */
+			val = utoa(G.global_argc ? G.global_argc-1 : 0);
+			break;
+		case '-': { /* active options */
+			/* Check set_mode() to see what option chars we support */
+			char *cp;
+			val = cp = G.optstring_buf;
+			if (G.o_opt[OPT_O_ERREXIT])
+				*cp++ = 'e';
+			if (G_interactive_fd)
+				*cp++ = 'i';
+			if (G_x_mode)
+				*cp++ = 'x';
+			/* If G.o_opt[OPT_O_NOEXEC] is true,
+			 * commands read but are not executed,
+			 * so $- can not execute too, 'n' is never seen in $-.
+			 */
+			if (G.opt_c)
+				*cp++ = 'c';
+			if (G.opt_s)
+				*cp++ = 's';
+			*cp = '\0';
+			break;
+		}
+		default:
+			val = get_local_var_value(var);
+		}
+	}
+
+	/* Handle any expansions */
+	if (exp_op == 'L') {
+		reinit_unicode_for_hush();
+		debug_printf_expand("expand: length(%s)=", val);
+		val = utoa(val ? unicode_strlen(val) : 0);
+		debug_printf_expand("%s\n", val);
+	} else if (exp_op) {
+		if (exp_op == '%' || exp_op == '#') {
+			/* Standard-mandated substring removal ops:
+			 * ${parameter%word} - remove smallest suffix pattern
+			 * ${parameter%%word} - remove largest suffix pattern
+			 * ${parameter#word} - remove smallest prefix pattern
+			 * ${parameter##word} - remove largest prefix pattern
+			 *
+			 * Word is expanded to produce a glob pattern.
+			 * Then var's value is matched to it and matching part removed.
+			 */
+			/* bash compat: if x is "" and no shrinking of it is possible,
+			 * inner ${...} is not evaluated. Example:
+			 *  unset b; : ${a%${b=B}}; echo $b
+			 * assignment b=B only happens if $a is not "".
+			 */
+			if (val && val[0]) {
+				char *t;
+				char *exp_exp_word;
+				char *loc;
+				unsigned scan_flags = pick_scan(exp_op, *exp_word);
+				if (exp_op == *exp_word)  /* ## or %% */
+					exp_word++;
+				debug_printf_expand("expand: exp_word:'%s'\n", exp_word);
+				exp_exp_word = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0);
+				if (exp_exp_word)
+					exp_word = exp_exp_word;
+				debug_printf_expand("expand: exp_word:'%s'\n", exp_word);
+				/*
+				 * HACK ALERT. We depend here on the fact that
+				 * G.global_argv and results of utoa and get_local_var_value
+				 * are actually in writable memory:
+				 * scan_and_match momentarily stores NULs there.
+				 */
+				t = (char*)val;
+				loc = scan_and_match(t, exp_word, scan_flags);
+				debug_printf_expand("op:%c str:'%s' pat:'%s' res:'%s'\n", exp_op, t, exp_word, loc);
+				free(exp_exp_word);
+				if (loc) { /* match was found */
+					if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
+						val = loc; /* take right part */
+					else /* %[%] */
+						val = to_be_freed = xstrndup(val, loc - val); /* left */
+				}
+			}
+		}
+#if BASH_PATTERN_SUBST
+		else if (exp_op == '/' || exp_op == '\\') {
+			/* It's ${var/[/]pattern[/repl]} thing.
+			 * Note that in encoded form it has TWO parts:
+			 * var/pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
+			 * and if // is used, it is encoded as \:
+			 * var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
+			 */
+			/* bash compat: if var is "", both pattern and repl
+			 * are still evaluated, if it is unset, then not:
+			 * unset b; a=; : ${a/z/${b=3}}; echo $b      # b=3
+			 * unset b; unset a; : ${a/z/${b=3}}; echo $b # b not set
+			 */
+			if (val /*&& val[0]*/) {
+				/* pattern uses non-standard expansion.
+				 * repl should be unbackslashed and globbed
+				 * by the usual expansion rules:
+				 *  >az >bz
+				 *  v='a bz'; echo "${v/a*z/a*z}" #prints "a*z"
+				 *  v='a bz'; echo "${v/a*z/\z}"  #prints "z"
+				 *  v='a bz'; echo ${v/a*z/a*z}   #prints "az"
+				 *  v='a bz'; echo ${v/a*z/\z}    #prints "z"
+				 * (note that a*z _pattern_ is never globbed!)
+				 */
+				char *pattern, *repl, *t;
+				pattern = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0);
+				if (!pattern)
+					pattern = xstrdup(exp_word);
+				debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
+				*p++ = SPECIAL_VAR_SYMBOL;
+				exp_word = p;
+				p = strchr(p, SPECIAL_VAR_SYMBOL);
+				*p = '\0';
+				repl = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 1);
+				debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
+				/* HACK ALERT. We depend here on the fact that
+				 * G.global_argv and results of utoa and get_local_var_value
+				 * are actually in writable memory:
+				 * replace_pattern momentarily stores NULs there. */
+				t = (char*)val;
+				to_be_freed = replace_pattern(t,
+						pattern,
+						(repl ? repl : exp_word),
+						exp_op);
+				if (to_be_freed) /* at least one replace happened */
+					val = to_be_freed;
+				free(pattern);
+				free(repl);
+			} else {
+				/* Unset variable always gives nothing */
+				//  a=; echo ${a/*/w}      # "w"
+				//  unset a; echo ${a/*/w} # ""
+				/* Just skip "replace" part */
+				*p++ = SPECIAL_VAR_SYMBOL;
+				p = strchr(p, SPECIAL_VAR_SYMBOL);
+				*p = '\0';
+			}
+		}
+#endif /* BASH_PATTERN_SUBST */
+		else if (exp_op == ':') {
+#if BASH_SUBSTR && ENABLE_FEATURE_SH_MATH
+			/* It's ${var:N[:M]} bashism.
+			 * Note that in encoded form it has TWO parts:
+			 * var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
+			 */
+			arith_t beg, len;
+			unsigned vallen;
+			const char *errmsg;
+
+			beg = expand_and_evaluate_arith(exp_word, &errmsg);
+			if (errmsg)
+				goto empty_result;
+			debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
+			*p++ = SPECIAL_VAR_SYMBOL;
+			exp_word = p;
+			p = strchr(p, SPECIAL_VAR_SYMBOL);
+			*p = '\0';
+			vallen = val ? strlen(val) : 0;
+			if (beg < 0) {
+				/* negative beg counts from the end */
+				beg = (arith_t)vallen + beg;
+			}
+			/* If expansion will be empty, do not even evaluate len */
+			if (!val || beg < 0 || beg > vallen) {
+				/* Why > vallen, not >=? bash:
+				 * unset b; a=ab; : ${a:2:${b=3}}; echo $b  # "", b=3 (!!!)
+				 * unset b; a=a; : ${a:2:${b=3}}; echo $b   # "", b not set
+				 */
+				goto empty_result;
+			}
+			len = expand_and_evaluate_arith(exp_word, &errmsg);
+			if (errmsg)
+				goto empty_result;
+			debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
+			debug_printf_varexp("from val:'%s'\n", val);
+			if (len < 0) {
+				/* in bash, len=-n means strlen()-n */
+				len = (arith_t)vallen - beg + len;
+				if (len < 0) /* bash compat */
+					msg_and_die_if_script("%s: substring expression < 0", var);
+			}
+			if (len <= 0 || !val /*|| beg >= vallen*/) {
+ empty_result:
+				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) */
+			msg_and_die_if_script("malformed ${%s:...}", var);
+			val = NULL;
+#endif
+		} else { /* one of "-=+?" */
+			/* Standard-mandated substitution ops:
+			 * ${var?word} - indicate error if unset
+			 *      If var is unset, word (or a message indicating it is unset
+			 *      if word is null) is written to standard error
+			 *      and the shell exits with a non-zero exit status.
+			 *      Otherwise, the value of var is substituted.
+			 * ${var-word} - use default value
+			 *      If var is unset, word is substituted.
+			 * ${var=word} - assign and use default value
+			 *      If var is unset, word is assigned to var.
+			 *      In all cases, final value of var is substituted.
+			 * ${var+word} - use alternative value
+			 *      If var is unset, null is substituted.
+			 *      Otherwise, word is substituted.
+			 *
+			 * Word is subjected to tilde expansion, parameter expansion,
+			 * command substitution, and arithmetic expansion.
+			 * If word is not needed, it is not expanded.
+			 *
+			 * Colon forms (${var:-word}, ${var:=word} etc) do the same,
+			 * but also treat null var as if it is unset.
+			 *
+			 * Word-splitting and single quote behavior:
+			 *
+			 * $ f() { for i; do echo "|$i|"; done; }
+			 *
+			 * $ x=; f ${x:?'x y' z}; echo $?
+			 * bash: x: x y z       # neither f nor "echo $?" executes
+			 * (if interactive, bash does not exit, but merely aborts to prompt. $? is set to 1)
+			 * $ x=; f "${x:?'x y' z}"
+			 * bash: x: x y z       # dash prints: dash: x: 'x y' z
+			 *
+			 * $ x=; f ${x:='x y' z}
+			 * |x|
+			 * |y|
+			 * |z|
+			 * $ x=; f "${x:='x y' z}"
+			 * |'x y' z|
+			 *
+			 * $ x=x; f ${x:+'x y' z}
+			 * |x y|
+			 * |z|
+			 * $ x=x; f "${x:+'x y' z}"
+			 * |'x y' z|
+			 *
+			 * $ x=; f ${x:-'x y' z}
+			 * |x y|
+			 * |z|
+			 * $ x=; f "${x:-'x y' z}"
+			 * |'x y' z|
+			 */
+			int use_word = (!val || ((exp_save == ':') && !val[0]));
+			if (exp_op == '+')
+				use_word = !use_word;
+			debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
+					(exp_save == ':') ? "true" : "false", use_word);
+			if (use_word) {
+				if (exp_op == '+' || exp_op == '-') {
+					/* ${var+word} - use alternative value */
+					/* ${var-word} - use default value */
+					n = encode_then_append_var_plusminus(output, n, exp_word,
+							/*dquoted:*/ (arg0 & 0x80)
+					);
+					val = NULL;
+				} else {
+					/* ${var?word} - indicate error if unset */
+					/* ${var=word} - assign and use default value */
+					to_be_freed = encode_then_expand_vararg(exp_word,
+							/*handle_squotes:*/ !(arg0 & 0x80),
+							/*unbackslash:*/ 0
+					);
+					if (to_be_freed)
+						exp_word = to_be_freed;
+					if (exp_op == '?') {
+						/* mimic bash message */
+						msg_and_die_if_script("%s: %s",
+							var,
+							exp_word[0]
+							? exp_word
+							: "parameter null or not set"
+							/* ash has more specific messages, a-la: */
+							/*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
+						);
+//TODO: how interactive bash aborts expansion mid-command?
+//It aborts the entire line, returns to prompt:
+// $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO
+// bash: x: x y z
+// $
+// ("echo YO" is not executed, neither the f function call)
+					} else {
+						val = exp_word;
+					}
+					if (exp_op == '=') {
+						/* ${var=[word]} or ${var:=[word]} */
+						if (isdigit(var[0]) || var[0] == '#') {
+							/* mimic bash message */
+							msg_and_die_if_script("$%s: cannot assign in this way", var);
+							val = NULL;
+						} else {
+							char *new_var = xasprintf("%s=%s", var, val);
+							set_local_var(new_var, /*flag:*/ 0);
+						}
+					}
+				}
+			}
+		} /* one of "-=+?" */
+
+		*exp_saveptr = exp_save;
+	} /* if (exp_op) */
+
+	arg[0] = arg0;
+	*pp = p;
+
+	n = append_str_maybe_ifs_split(output, n, first_ch, val);
+
+	free(to_be_freed);
+	return n;
+}
+
+/* Expand all variable references in given string, adding words to list[]
+ * at n, n+1,... positions. Return updated n (so that list[n] is next one
+ * to be filled). This routine is extremely tricky: has to deal with
+ * variables/parameters with whitespace, $* and $@, and constructs like
+ * 'echo -$*-'. If you play here, you must run testsuite afterwards! */
+static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
+{
+	/* output->o_expflags & EXP_FLAG_SINGLEWORD (0x80) if we are in
+	 * expansion of right-hand side of assignment == 1-element expand.
+	 */
+	char cant_be_null = 0; /* only bit 0x80 matters */
+	char *p;
+
+	debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
+			!!(output->o_expflags & EXP_FLAG_SINGLEWORD));
+	debug_print_list("expand_vars_to_list[0]", output, n);
+
+	while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
+		char first_ch;
+#if ENABLE_FEATURE_SH_MATH
+		char arith_buf[sizeof(arith_t)*3 + 2];
+#endif
+
+		if (output->ended_in_ifs) {
+			o_addchr(output, '\0');
+			n = o_save_ptr(output, n);
+			output->ended_in_ifs = 0;
+		}
+
+		o_addblock(output, arg, p - arg);
+		debug_print_list("expand_vars_to_list[1]", output, n);
+		arg = ++p;
+		p = strchr(p, SPECIAL_VAR_SYMBOL);
+
+		/* Fetch special var name (if it is indeed one of them)
+		 * and quote bit, force the bit on if singleword expansion -
+		 * important for not getting v=$@ expand to many words. */
+		first_ch = arg[0] | (output->o_expflags & EXP_FLAG_SINGLEWORD);
+
+		/* Is this variable quoted and thus expansion can't be null?
+		 * "$@" is special. Even if quoted, it can still
+		 * expand to nothing (not even an empty string),
+		 * thus it is excluded. */
+		if ((first_ch & 0x7f) != '@')
+			cant_be_null |= first_ch;
+
+		switch (first_ch & 0x7f) {
+		/* Highest bit in first_ch indicates that var is double-quoted */
+		case '*':
+		case '@': {
+			int i;
+			if (!G.global_argv[1])
+				break;
+			i = 1;
+			cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
+			if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
+				while (G.global_argv[i]) {
+					n = expand_on_ifs(output, n, G.global_argv[i]);
+					debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
+					if (G.global_argv[i++][0] && G.global_argv[i]) {
+						/* this argv[] is not empty and not last:
+						 * put terminating NUL, start new word */
+						o_addchr(output, '\0');
+						debug_print_list("expand_vars_to_list[2]", output, n);
+						n = o_save_ptr(output, n);
+						debug_print_list("expand_vars_to_list[3]", output, n);
+					}
+				}
+			} else
+			/* If EXP_FLAG_SINGLEWORD, we handle assignment 'a=....$@.....'
+			 * and in this case should treat it like '$*' - see 'else...' below */
+			if (first_ch == (char)('@'|0x80)  /* quoted $@ */
+			 && !(output->o_expflags & EXP_FLAG_SINGLEWORD) /* not v="$@" case */
+			) {
+				while (1) {
+					o_addQstr(output, G.global_argv[i]);
+					if (++i >= G.global_argc)
+						break;
+					o_addchr(output, '\0');
+					debug_print_list("expand_vars_to_list[4]", output, n);
+					n = o_save_ptr(output, n);
+				}
+			} else { /* quoted $* (or v="$@" case): add as one word */
+				while (1) {
+					o_addQstr(output, G.global_argv[i]);
+					if (!G.global_argv[++i])
+						break;
+					if (G.ifs[0])
+						o_addchr(output, G.ifs[0]);
+				}
+				output->has_quoted_part = 1;
+			}
+			break;
+		}
+		case SPECIAL_VAR_SYMBOL: {
+			/* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
+			/* "Empty variable", used to make "" etc to not disappear */
+			output->has_quoted_part = 1;
+			cant_be_null = 0x80;
+			arg++;
+			break;
+		}
+		case SPECIAL_VAR_QUOTED_SVS:
+			/* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_QUOTED_SVS><SPECIAL_VAR_SYMBOL> */
+			/* "^C variable", represents literal ^C char (possible in scripts) */
+			o_addchr(output, SPECIAL_VAR_SYMBOL);
+			arg++;
+			break;
+#if ENABLE_HUSH_TICK
+		case '`': {
+			/* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
+			o_string subst_result = NULL_O_STRING;
+
+			*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
+			arg++;
+			/* Can't just stuff it into output o_string,
+			 * expanded result may need to be globbed
+			 * and $IFS-split */
+			debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
+			G.last_exitcode = process_command_subs(&subst_result, arg);
+			G.expand_exitcode = G.last_exitcode;
+			debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
+			n = append_str_maybe_ifs_split(output, n, first_ch, subst_result.data);
+			o_free(&subst_result);
+			break;
+		}
+#endif
+#if ENABLE_FEATURE_SH_MATH
+		case '+': {
+			/* <SPECIAL_VAR_SYMBOL>+arith<SPECIAL_VAR_SYMBOL> */
+			arith_t res;
+
+			arg++; /* skip '+' */
+			*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
+			debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
+			res = expand_and_evaluate_arith(arg, NULL);
+			debug_printf_subst("ARITH RES '"ARITH_FMT"'\n", res);
+			sprintf(arith_buf, ARITH_FMT, res);
+			if (res < 0
+			 && first_ch == (char)('+'|0x80)
+			/* && (output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS) */
+			) {
+				/* Quoted negative ariths, like filename[0"$((-9))"],
+				 * should not be interpreted as glob ranges.
+				 * Convert leading '-' to '\-':
+				 */
+				o_grow_by(output, 1);
+				output->data[output->length++] = '\\';
+			}
+			o_addstr(output, arith_buf);
+			break;
+		}
+#endif
+		default:
+			/* <SPECIAL_VAR_SYMBOL>varname[ops]<SPECIAL_VAR_SYMBOL> */
+			n = expand_one_var(output, n, first_ch, arg, &p);
+			break;
+		} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
+
+		/* Restore NULL'ed SPECIAL_VAR_SYMBOL.
+		 * Do the check to avoid writing to a const string. */
+		if (*p != SPECIAL_VAR_SYMBOL)
+			*p = SPECIAL_VAR_SYMBOL;
+		arg = ++p;
+	} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
+
+	if (*arg) {
+		/* handle trailing string */
+		if (output->ended_in_ifs) {
+			o_addchr(output, '\0');
+			n = o_save_ptr(output, n);
+		}
+		debug_print_list("expand_vars_to_list[a]", output, n);
+		/* this part is literal, and it was already pre-quoted
+		 * if needed (much earlier), do not use o_addQstr here!
+		 */
+		o_addstr(output, arg);
+		debug_print_list("expand_vars_to_list[b]", output, n);
+	} else
+	if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
+	 && !(cant_be_null & 0x80)   /* and all vars were not quoted */
+	 && !output->has_quoted_part
+	) {
+		n--;
+		/* allow to reuse list[n] later without re-growth */
+		output->has_empty_slot = 1;
+	}
+
+	return n;
+}
+
+static char **expand_variables(char **argv, unsigned expflags)
+{
+	int n;
+	char **list;
+	o_string output = NULL_O_STRING;
+
+	output.o_expflags = expflags;
+
+	n = 0;
+	for (;;) {
+		/* go to next list[n] */
+		output.ended_in_ifs = 0;
+		n = o_save_ptr(&output, n);
+
+		if (!*argv)
+			break;
+
+		/* expand argv[i] */
+		n = expand_vars_to_list(&output, n, *argv++);
+		/* if (!output->has_empty_slot) -- need this?? */
+			o_addchr(&output, '\0');
+	}
+	debug_print_list("expand_variables", &output, n);
+
+	/* output.data (malloced in one block) gets returned in "list" */
+	list = o_finalize_list(&output, n);
+	debug_print_strings("expand_variables[1]", list);
+	return list;
+}
+
+static char **expand_strvec_to_strvec(char **argv)
+{
+	return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS);
+}
+
+#if defined(CMD_SINGLEWORD_NOGLOB) || defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
+{
+	return expand_variables(argv, EXP_FLAG_SINGLEWORD);
+}
+#endif
+
+/* Used for expansion of right hand of assignments,
+ * $((...)), heredocs, variable expansion parts.
+ *
+ * NB: should NOT do globbing!
+ * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*"
+ */
+static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash)
+{
+#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
+	const int do_unbackslash = 1;
+	const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS;
+#endif
+	char *argv[2], **list;
+
+	debug_printf_expand("string_to_string<='%s'\n", str);
+	/* This is generally an optimization, but it also
+	 * handles "", which otherwise trips over !list[0] check below.
+	 * (is this ever happens that we actually get str="" here?)
+	 */
+	if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) {
+		//TODO: Can use on strings with \ too, just unbackslash() them?
+		debug_printf_expand("string_to_string(fast)=>'%s'\n", str);
+		return xstrdup(str);
+	}
+
+	argv[0] = (char*)str;
+	argv[1] = NULL;
+	list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD);
+	if (!list[0]) {
+		/* Example where it happens:
+		 * x=; echo ${x:-"$@"}
+		 */
+		((char*)list)[0] = '\0';
+	} else {
+		if (HUSH_DEBUG)
+			if (list[1])
+				bb_simple_error_msg_and_die("BUG in varexp2");
+		/* actually, just move string 2*sizeof(char*) bytes back */
+		overlapping_strcpy((char*)list, list[0]);
+		if (do_unbackslash)
+			unbackslash((char*)list);
+	}
+	debug_printf_expand("string_to_string=>'%s'\n", (char*)list);
+	return (char*)list;
+}
+
+#if 0
+static char* expand_strvec_to_string(char **argv)
+{
+	char **list;
+
+	list = expand_variables(argv, EXP_FLAG_SINGLEWORD);
+	/* Convert all NULs to spaces */
+	if (list[0]) {
+		int n = 1;
+		while (list[n]) {
+			if (HUSH_DEBUG)
+				if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
+					bb_error_msg_and_die("BUG in varexp3");
+			/* bash uses ' ' regardless of $IFS contents */
+			list[n][-1] = ' ';
+			n++;
+		}
+	}
+	overlapping_strcpy((char*)list, list[0] ? list[0] : "");
+	debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
+	return (char*)list;
+}
+#endif
+
+static char **expand_assignments(char **argv, int count)
+{
+	int i;
+	char **p;
+
+	G.expanded_assignments = p = NULL;
+	/* Expand assignments into one string each */
+	for (i = 0; i < count; i++) {
+		p = add_string_to_strings(p,
+			expand_string_to_string(argv[i],
+				EXP_FLAG_ESC_GLOB_CHARS,
+				/*unbackslash:*/ 1
+			)
+		);
+		G.expanded_assignments = p;
+	}
+	G.expanded_assignments = NULL;
+	return p;
+}
+
+
+static void switch_off_special_sigs(unsigned mask)
+{
+	unsigned sig = 0;
+	while ((mask >>= 1) != 0) {
+		sig++;
+		if (!(mask & 1))
+			continue;
+#if ENABLE_HUSH_TRAP
+		if (G_traps) {
+			if (G_traps[sig] && !G_traps[sig][0])
+				/* trap is '', has to remain SIG_IGN */
+				continue;
+			free(G_traps[sig]);
+			G_traps[sig] = NULL;
+		}
+#endif
+		/* We are here only if no trap or trap was not '' */
+		install_sighandler(sig, SIG_DFL);
+	}
+}
+
+#if BB_MMU
+/* never called */
+void re_execute_shell(char ***to_free, const char *s,
+		char *g_argv0, char **g_argv,
+		char **builtin_argv) NORETURN;
+
+static void reset_traps_to_defaults(void)
+{
+	/* This function is always called in a child shell
+	 * after fork (not vfork, NOMMU doesn't use this function).
+	 */
+	IF_HUSH_TRAP(unsigned sig;)
+	unsigned mask;
+
+	/* Child shells are not interactive.
+	 * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+	 * Testcase: (while :; do :; done) + ^Z should background.
+	 * Same goes for SIGTERM, SIGHUP, SIGINT.
+	 */
+	mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask;
+	if (!G_traps && !mask)
+		return; /* already no traps and no special sigs */
+
+	/* Switch off special sigs */
+	switch_off_special_sigs(mask);
+# if ENABLE_HUSH_JOB
+	G_fatal_sig_mask = 0;
+# endif
+	G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+	/* SIGQUIT,SIGCHLD and maybe SPECIAL_JOBSTOP_SIGS
+	 * remain set in G.special_sig_mask */
+
+# if ENABLE_HUSH_TRAP
+	if (!G_traps)
+		return;
+
+	/* Reset all sigs to default except ones with empty traps */
+	for (sig = 0; sig < NSIG; sig++) {
+		if (!G_traps[sig])
+			continue; /* no trap: nothing to do */
+		if (!G_traps[sig][0])
+			continue; /* empty trap: has to remain SIG_IGN */
+		/* sig has non-empty trap, reset it: */
+		free(G_traps[sig]);
+		G_traps[sig] = NULL;
+		/* There is no signal for trap 0 (EXIT) */
+		if (sig == 0)
+			continue;
+		install_sighandler(sig, pick_sighandler(sig));
+	}
+# endif
+}
+
+#else /* !BB_MMU */
+
+static void re_execute_shell(char ***to_free, const char *s,
+		char *g_argv0, char **g_argv,
+		char **builtin_argv) NORETURN;
+static void re_execute_shell(char ***to_free, const char *s,
+		char *g_argv0, char **g_argv,
+		char **builtin_argv)
+{
+# define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x"))
+	/* delims + 2 * (number of bytes in printed hex numbers) */
+	char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)];
+	char *heredoc_argv[4];
+	struct variable *cur;
+# if ENABLE_HUSH_FUNCTIONS
+	struct function *funcp;
+# endif
+	char **argv, **pp;
+	unsigned cnt;
+	unsigned long long empty_trap_mask;
+
+	if (!g_argv0) { /* heredoc */
+		argv = heredoc_argv;
+		argv[0] = (char *) G.argv0_for_re_execing;
+		argv[1] = (char *) "-<";
+		argv[2] = (char *) s;
+		argv[3] = NULL;
+		pp = &argv[3]; /* used as pointer to empty environment */
+		goto do_exec;
+	}
+
+	cnt = 0;
+	pp = builtin_argv;
+	if (pp) while (*pp++)
+		cnt++;
+
+	empty_trap_mask = 0;
+	if (G_traps) {
+		int sig;
+		for (sig = 1; sig < NSIG; sig++) {
+			if (G_traps[sig] && !G_traps[sig][0])
+				empty_trap_mask |= 1LL << sig;
+		}
+	}
+
+	sprintf(param_buf, NOMMU_HACK_FMT
+			, (unsigned) G.root_pid
+			, (unsigned) G.root_ppid
+			, (unsigned) G.last_bg_pid
+			, (unsigned) G.last_exitcode
+			, cnt
+			, empty_trap_mask
+			IF_HUSH_LOOPS(, G.depth_of_loop)
+			);
+# undef NOMMU_HACK_FMT
+	/* 1:hush 2:-$<pid>:<pid>:<exitcode>:<etc...> <vars...> <funcs...>
+	 * 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL
+	 */
+	cnt += 6;
+	for (cur = G.top_var; cur; cur = cur->next) {
+		if (!cur->flg_export || cur->flg_read_only)
+			cnt += 2;
+	}
+# if ENABLE_HUSH_FUNCTIONS
+	for (funcp = G.top_func; funcp; funcp = funcp->next)
+		cnt += 3;
+# endif
+	pp = g_argv;
+	while (*pp++)
+		cnt++;
+	*to_free = argv = pp = xzalloc(sizeof(argv[0]) * cnt);
+	*pp++ = (char *) G.argv0_for_re_execing;
+	*pp++ = param_buf;
+	for (cur = G.top_var; cur; cur = cur->next) {
+		if (strcmp(cur->varstr, hush_version_str) == 0)
+			continue;
+		if (cur->flg_read_only) {
+			*pp++ = (char *) "-R";
+			*pp++ = cur->varstr;
+		} else if (!cur->flg_export) {
+			*pp++ = (char *) "-V";
+			*pp++ = cur->varstr;
+		}
+	}
+# if ENABLE_HUSH_FUNCTIONS
+	for (funcp = G.top_func; funcp; funcp = funcp->next) {
+		*pp++ = (char *) "-F";
+		*pp++ = funcp->name;
+		*pp++ = funcp->body_as_string;
+	}
+# endif
+	/* We can pass activated traps here. Say, -Tnn:trap_string
+	 *
+	 * However, POSIX says that subshells reset signals with traps
+	 * to SIG_DFL.
+	 * I tested bash-3.2 and it not only does that with true subshells
+	 * of the form ( list ), but with any forked children shells.
+	 * I set trap "echo W" WINCH; and then tried:
+	 *
+	 * { echo 1; sleep 20; echo 2; } &
+	 * while true; do echo 1; sleep 20; echo 2; break; done &
+	 * true | { echo 1; sleep 20; echo 2; } | cat
+	 *
+	 * In all these cases sending SIGWINCH to the child shell
+	 * did not run the trap. If I add trap "echo V" WINCH;
+	 * _inside_ group (just before echo 1), it works.
+	 *
+	 * I conclude it means we don't need to pass active traps here.
+	 */
+	*pp++ = (char *) "-c";
+	*pp++ = (char *) s;
+	if (builtin_argv) {
+		while (*++builtin_argv)
+			*pp++ = *builtin_argv;
+		*pp++ = (char *) "";
+	}
+	*pp++ = g_argv0;
+	while (*g_argv)
+		*pp++ = *g_argv++;
+	/* *pp = NULL; - is already there */
+	pp = environ;
+
+ do_exec:
+	debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
+	/* Don't propagate SIG_IGN to the child */
+	if (SPECIAL_JOBSTOP_SIGS != 0)
+		switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
+	execve(bb_busybox_exec_path, argv, pp);
+	/* Fallback. Useful for init=/bin/hush usage etc */
+	if (argv[0][0] == '/')
+		execve(argv[0], argv, pp);
+	xfunc_error_retval = 127;
+	bb_simple_error_msg_and_die("can't re-execute the shell");
+}
+#endif  /* !BB_MMU */
+
+
+static int run_and_free_list(struct pipe *pi);
+
+/* Executing from string: eval, sh -c '...'
+ *          or from file: /etc/profile, . file, sh <script>, sh (intereactive)
+ * end_trigger controls how often we stop parsing
+ * NUL: parse all, execute, return
+ * ';': parse till ';' or newline, execute, repeat till EOF
+ */
+static void parse_and_run_stream(struct in_str *inp, int end_trigger)
+{
+	/* Why we need empty flag?
+	 * An obscure corner case "false; ``; echo $?":
+	 * empty command in `` should still set $? to 0.
+	 * But we can't just set $? to 0 at the start,
+	 * this breaks "false; echo `echo $?`" case.
+	 */
+	bool empty = 1;
+	while (1) {
+		struct pipe *pipe_list;
+
+#if ENABLE_HUSH_INTERACTIVE
+		if (end_trigger == ';') {
+			G.promptmode = 0; /* PS1 */
+			debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
+		}
+#endif
+		pipe_list = parse_stream(NULL, NULL, inp, end_trigger);
+		if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */
+			/* If we are in "big" script
+			 * (not in `cmd` or something similar)...
+			 */
+			if (pipe_list == ERR_PTR && end_trigger == ';') {
+				/* Discard cached input (rest of line) */
+				int ch = inp->last_char;
+				while (ch != EOF && ch != '\n') {
+					//bb_error_msg("Discarded:'%c'", ch);
+					ch = i_getch(inp);
+				}
+				/* Force prompt */
+				inp->p = NULL;
+				/* This stream isn't empty */
+				empty = 0;
+				continue;
+			}
+			if (!pipe_list && empty)
+				G.last_exitcode = 0;
+			break;
+		}
+		debug_print_tree(pipe_list, 0);
+		debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
+		run_and_free_list(pipe_list);
+		empty = 0;
+		if (G_flag_return_in_progress == 1)
+			break;
+	}
+}
+
+static void parse_and_run_string(const char *s)
+{
+	struct in_str input;
+	//IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+
+	setup_string_in_str(&input, s);
+	parse_and_run_stream(&input, '\0');
+	//IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
+}
+
+static void parse_and_run_file(HFILE *fp)
+{
+	struct in_str input;
+	IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+
+	IF_HUSH_LINENO_VAR(G.parse_lineno = 1;)
+	setup_file_in_str(&input, fp);
+	parse_and_run_stream(&input, ';');
+	IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
+}
+
+#if ENABLE_HUSH_TICK
+static int generate_stream_from_string(const char *s, pid_t *pid_p)
+{
+	pid_t pid;
+	int channel[2];
+# if !BB_MMU
+	char **to_free = NULL;
+# endif
+
+	xpipe(channel);
+	pid = BB_MMU ? xfork() : xvfork();
+	if (pid == 0) { /* child */
+		disable_restore_tty_pgrp_on_exit();
+		/* Process substitution is not considered to be usual
+		 * 'command execution'.
+		 * SUSv3 says ctrl-Z should be ignored, ctrl-C should not.
+		 */
+		bb_signals(0
+			+ (1 << SIGTSTP)
+			+ (1 << SIGTTIN)
+			+ (1 << SIGTTOU)
+			, SIG_IGN);
+		close(channel[0]); /* NB: close _first_, then move fd! */
+		xmove_fd(channel[1], 1);
+# if ENABLE_HUSH_TRAP
+		/* Awful hack for `trap` or $(trap).
+		 *
+		 * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html
+		 * contains an example where "trap" is executed in a subshell:
+		 *
+		 * save_traps=$(trap)
+		 * ...
+		 * eval "$save_traps"
+		 *
+		 * Standard does not say that "trap" in subshell shall print
+		 * parent shell's traps. It only says that its output
+		 * must have suitable form, but then, in the above example
+		 * (which is not supposed to be normative), it implies that.
+		 *
+		 * bash (and probably other shell) does implement it
+		 * (traps are reset to defaults, but "trap" still shows them),
+		 * but as a result, "trap" logic is hopelessly messed up:
+		 *
+		 * # trap
+		 * trap -- 'echo Ho' SIGWINCH  <--- we have a handler
+		 * # (trap)        <--- trap is in subshell - no output (correct, traps are reset)
+		 * # true | trap   <--- trap is in subshell - no output (ditto)
+		 * # echo `true | trap`    <--- in subshell - output (but traps are reset!)
+		 * trap -- 'echo Ho' SIGWINCH
+		 * # echo `(trap)`         <--- in subshell in subshell - output
+		 * trap -- 'echo Ho' SIGWINCH
+		 * # echo `true | (trap)`  <--- in subshell in subshell in subshell - output!
+		 * trap -- 'echo Ho' SIGWINCH
+		 *
+		 * The rules when to forget and when to not forget traps
+		 * get really complex and nonsensical.
+		 *
+		 * Our solution: ONLY bare $(trap) or `trap` is special.
+		 */
+		s = skip_whitespace(s);
+		if (is_prefixed_with(s, "trap")
+		 && skip_whitespace(s + 4)[0] == '\0'
+		) {
+			static const char *const argv[] = { NULL, NULL };
+			builtin_trap((char**)argv);
+			fflush_all(); /* important */
+			_exit(0);
+		}
+# endif
+# if BB_MMU
+		/* Prevent it from trying to handle ctrl-z etc */
+		IF_HUSH_JOB(G.run_list_level = 1;)
+		CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
+		reset_traps_to_defaults();
+		IF_HUSH_MODE_X(G.x_mode_depth++;)
+		//bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth);
+		parse_and_run_string(s);
+		_exit(G.last_exitcode);
+# else
+	/* We re-execute after vfork on NOMMU. This makes this script safe:
+	 * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
+	 * huge=`cat BIG` # was blocking here forever
+	 * echo OK
+	 */
+		re_execute_shell(&to_free,
+				s,
+				G.global_argv[0],
+				G.global_argv + 1,
+				NULL);
+# endif
+	}
+
+	/* parent */
+	*pid_p = pid;
+# if ENABLE_HUSH_FAST
+	G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in generate_stream_from_string:"
+//		" G.count_SIGCHLD:%d G.handled_SIGCHLD:%d",
+//		getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+# endif
+	enable_restore_tty_pgrp_on_exit();
+# if !BB_MMU
+	free(to_free);
+# endif
+	close(channel[1]);
+	return channel[0];
+}
+
+/* Return code is exit status of the process that is run. */
+static int process_command_subs(o_string *dest, const char *s)
+{
+	FILE *fp;
+	pid_t pid;
+	int status, ch, eol_cnt;
+
+	fp = xfdopen_for_read(generate_stream_from_string(s, &pid));
+
+	/* Now send results of command back into original context */
+	eol_cnt = 0;
+	while ((ch = getc(fp)) != EOF) {
+		if (ch == '\0')
+			continue;
+		if (ch == '\n') {
+			eol_cnt++;
+			continue;
+		}
+		while (eol_cnt) {
+			o_addchr(dest, '\n');
+			eol_cnt--;
+		}
+		o_addQchr(dest, ch);
+	}
+
+	debug_printf("done reading from `cmd` pipe, closing it\n");
+	fclose(fp);
+	/* We need to extract exitcode. Test case
+	 * "true; echo `sleep 1; false` $?"
+	 * should print 1 */
+	safe_waitpid(pid, &status, 0);
+	debug_printf("child exited. returning its exitcode:%d\n", WEXITSTATUS(status));
+	return WEXITSTATUS(status);
+}
+#endif /* ENABLE_HUSH_TICK */
+
+
+static void setup_heredoc(struct redir_struct *redir)
+{
+	struct fd_pair pair;
+	pid_t pid;
+	int len, written;
+	/* the _body_ of heredoc (misleading field name) */
+	const char *heredoc = redir->rd_filename;
+	char *expanded;
+#if !BB_MMU
+	char **to_free;
+#endif
+
+	expanded = NULL;
+	if (!(redir->rd_dup & HEREDOC_QUOTED)) {
+		expanded = encode_then_expand_string(heredoc);
+		if (expanded)
+			heredoc = expanded;
+	}
+	len = strlen(heredoc);
+
+	close(redir->rd_fd); /* often saves dup2+close in xmove_fd */
+	xpiped_pair(pair);
+	xmove_fd(pair.rd, redir->rd_fd);
+
+	/* Try writing without forking. Newer kernels have
+	 * dynamically growing pipes. Must use non-blocking write! */
+	ndelay_on(pair.wr);
+	while (1) {
+		written = write(pair.wr, heredoc, len);
+		if (written <= 0)
+			break;
+		len -= written;
+		if (len == 0) {
+			close(pair.wr);
+			free(expanded);
+			return;
+		}
+		heredoc += written;
+	}
+	ndelay_off(pair.wr);
+
+	/* Okay, pipe buffer was not big enough */
+	/* Note: we must not create a stray child (bastard? :)
+	 * for the unsuspecting parent process. Child creates a grandchild
+	 * and exits before parent execs the process which consumes heredoc
+	 * (that exec happens after we return from this function) */
+#if !BB_MMU
+	to_free = NULL;
+#endif
+	pid = xvfork();
+	if (pid == 0) {
+		/* child */
+		disable_restore_tty_pgrp_on_exit();
+		pid = BB_MMU ? xfork() : xvfork();
+		if (pid != 0)
+			_exit(0);
+		/* grandchild */
+		close(redir->rd_fd); /* read side of the pipe */
+#if BB_MMU
+		full_write(pair.wr, heredoc, len); /* may loop or block */
+		_exit(0);
+#else
+		/* Delegate blocking writes to another process */
+		xmove_fd(pair.wr, STDOUT_FILENO);
+		re_execute_shell(&to_free, heredoc, NULL, NULL, NULL);
+#endif
+	}
+	/* parent */
+#if ENABLE_HUSH_FAST
+	G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
+	enable_restore_tty_pgrp_on_exit();
+#if !BB_MMU
+	free(to_free);
+#endif
+	close(pair.wr);
+	free(expanded);
+	wait(NULL); /* wait till child has died */
+}
+
+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 *append_squirrel(struct squirrel *sq, int i, int orig, int moved)
+{
+	sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
+	sq[i].orig_fd = orig;
+	sq[i].moved_to = moved;
+	sq[i+1].orig_fd = -1; /* end marker */
+	return sq;
+}
+
+static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
+{
+	int moved_to;
+	int i;
+
+	i = 0;
+	if (sq) for (; sq[i].orig_fd >= 0; i++) {
+		/* If we collide with an already moved fd... */
+		if (fd == sq[i].moved_to) {
+			sq[i].moved_to = dup_CLOEXEC(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 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;
+		}
+	}
+
+	/* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
+	moved_to = dup_CLOEXEC(fd, avoid_fd);
+	debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, moved_to);
+	if (moved_to < 0 && errno != EBADF)
+		xfunc_die();
+	return append_squirrel(sq, i, fd, moved_to);
+}
+
+static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd)
+{
+	int i;
+
+	i = 0;
+	if (sq) for (; sq[i].orig_fd >= 0; i++) {
+		/* If we collide with an already moved fd... */
+		if (fd == sq[i].orig_fd) {
+			/* Examples:
+			 * "echo 3>FILE 3>&- 3>FILE"
+			 * "echo 3>&- 3>FILE"
+			 * No need for last redirect to insert
+			 * another "need to close 3" indicator.
+			 */
+			debug_printf_redir("redirect_fd %d: already moved or closed\n", fd);
+			return sq;
+		}
+	}
+
+	debug_printf_redir("redirect_fd %d: previous fd was closed\n", fd);
+	return append_squirrel(sq, i, fd, -1);
+}
+
+/* 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_fd_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 /* don't trigger for G_interactive_fd == 0 (that's "not interactive" flag) */
+	 && fd == G_interactive_fd
+	) {
+		/* Testcase: "ls -l /proc/$$/fd 255>&-" should work */
+		G_interactive_fd = xdup_CLOEXEC_and_close(G_interactive_fd, 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)
+	 * in redirect in a [v]forked child?
+	 */
+	if (sqp == NULL) {
+		/* No need to move script fds.
+		 * For NOMMU case, it's actively wrong: we'd change ->fd
+		 * fields in memory for the parent, but parent's fds
+		 * aren't moved, it would use wrong fd!
+		 * Reproducer: "cmd 3>FILE" in script.
+		 * If we would call move_HFILEs_on_redirect(), child would:
+		 *  fcntl64(3, F_DUPFD_CLOEXEC, 10)   = 10
+		 *  close(3)                          = 0
+		 * and change ->fd to 10 if fd#3 is a script fd. WRONG.
+		 */
+		//bb_error_msg("sqp == NULL: [v]forked child");
+		return 0;
+	}
+
+	/* If this one of script's fds? */
+	if (move_HFILEs_on_redirect(fd, avoid_fd))
+		return 1; /* yes. "we closed fd" (actually moved it) */
+
+	/* Are we called for "exec 3>FILE"? Came through
+	 * redirect_and_varexp_helper(squirrel=ERR_PTR) -> setup_redirects(ERR_PTR)
+	 * This case used to fail for this script:
+	 *  exec 3>FILE
+	 *  echo Ok
+	 *  ...100000 more lines...
+	 *  echo Ok
+	 * as follows:
+	 *  read(3, "exec 3>FILE\necho Ok\necho Ok"..., 1024) = 1024
+	 *  open("FILE", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 4
+	 *  dup2(4, 3)                        = 3
+	 *  ^^^^^^^^ oops, we lost fd#3 opened to our script!
+	 *  close(4)                          = 0
+	 *  write(1, "Ok\n", 3)               = 3
+	 *  ...                               = 3
+	 *  write(1, "Ok\n", 3)               = 3
+	 *  read(3, 0x94fbc08, 1024)          = -1 EBADF (Bad file descriptor)
+	 *  ^^^^^^^^ oops, wrong fd!!!
+	 * With this case separate from sqp == NULL and *after* move_HFILEs,
+	 * it now works:
+	 */
+	if (sqp == ERR_PTR) {
+		/* Don't preserve redirected fds: exec is _meant_ to change these */
+		//bb_error_msg("sqp == ERR_PTR: exec >FILE");
+		return 0;
+	}
+
+	/* 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(struct squirrel *sq)
+{
+	if (sq) {
+		int i;
+		for (i = 0; sq[i].orig_fd >= 0; i++) {
+			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);
+			}
+		}
+		free(sq);
+	}
+	if (G.HFILE_stdin
+	 && G.HFILE_stdin->fd > STDIN_FILENO
+	/* we compare > STDIN, not == STDIN, since hfgetc()
+	 * closes fd and sets ->fd to -1 if EOF is reached.
+	 * Testcase: echo 'pwd' | hush
+	 */
+	) {
+		/* Testcase: interactive "read r <FILE; echo $r; read r; echo $r".
+		 * Redirect moves ->fd to e.g. 10,
+		 * and it is not restored above (we do not restore script fds
+		 * after redirects, we just use new, "moved" fds).
+		 * However for stdin, get_user_input() -> read_line_input(),
+		 * and read builtin, depend on fd == STDIN_FILENO.
+		 */
+		debug_printf_redir("restoring %d to stdin\n", G.HFILE_stdin->fd);
+		xmove_fd(G.HFILE_stdin->fd, STDIN_FILENO);
+		G.HFILE_stdin->fd = STDIN_FILENO;
+	}
+
+	/* If moved, G_interactive_fd stays on new fd, not restoring it */
+}
+
+#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
+static void close_saved_fds_and_FILE_fds(void)
+{
+	if (G_interactive_fd)
+		close(G_interactive_fd);
+	close_all_HFILE_list();
+}
+#endif
+
+static int internally_opened_fd(int fd, struct squirrel *sq)
+{
+	int i;
+
+#if ENABLE_HUSH_INTERACTIVE
+	if (fd == G_interactive_fd)
+		return 1;
+#endif
+	/* If this one of script's fds? */
+	if (fd_in_HFILEs(fd))
+		return 1;
+
+	if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
+		if (fd == sq[i].moved_to)
+			return 1;
+	}
+	return 0;
+}
+
+/* squirrel != NULL means we squirrel away copies of stdin, stdout,
+ * and stderr if they are redirected. */
+static int setup_redirects(struct command *prog, struct squirrel **sqp)
+{
+	struct redir_struct *redir;
+
+	for (redir = prog->redirects; redir; redir = redir->next) {
+		int newfd;
+		int closed;
+
+		if (redir->rd_type == REDIRECT_HEREDOC2) {
+			/* "rd_fd<<HERE" case */
+			save_fd_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
+			/* for REDIRECT_HEREDOC2, rd_filename holds _contents_
+			 * of the heredoc */
+			debug_printf_redir("set heredoc '%s'\n",
+					redir->rd_filename);
+			setup_heredoc(redir);
+			continue;
+		}
+
+		if (redir->rd_dup == REDIRFD_TO_FILE) {
+			/* "rd_fd<*>file" case (<*> is <,>,>>,<>) */
+			char *p;
+			int mode;
+
+			if (redir->rd_filename == NULL) {
+				/* Examples:
+				 * "cmd >" (no filename)
+				 * "cmd > <file" (2nd redirect starts too early)
+				 */
+				syntax_error("invalid redirect");
+				continue;
+			}
+			mode = redir_table[redir->rd_type].mode;
+			p = expand_string_to_string(redir->rd_filename,
+				EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1);
+			newfd = open_or_warn(p, mode);
+			free(p);
+			if (newfd < 0) {
+				/* Error message from open_or_warn can be lost
+				 * if stderr has been redirected, but bash
+				 * and ash both lose it as well
+				 * (though zsh doesn't!)
+				 */
+				return 1;
+			}
+			if (newfd == redir->rd_fd && sqp) {
+				/* open() gave us precisely the fd we wanted.
+				 * This means that this fd was not busy
+				 * (not opened to anywhere).
+				 * Remember to close it on restore:
+				 */
+				*sqp = add_squirrel_closed(*sqp, newfd);
+				debug_printf_redir("redir to previously closed fd %d\n", newfd);
+			}
+		} else {
+			/* "rd_fd>&rd_dup" or "rd_fd>&-" case */
+			newfd = redir->rd_dup;
+		}
+
+		if (newfd == redir->rd_fd)
+			continue;
+
+		/* if "N>FILE": move newfd to redir->rd_fd */
+		/* if "N>&M": dup newfd to redir->rd_fd */
+		/* if "N>&-": close redir->rd_fd (newfd is REDIRFD_CLOSE) */
+
+		closed = save_fd_on_redirect(redir->rd_fd, /*avoid:*/ newfd, sqp);
+		if (newfd == REDIRFD_CLOSE) {
+			/* "N>&-" means "close me" */
+			if (!closed) {
+				/* ^^^ optimization: saving may already
+				 * have closed it. If not... */
+				close(redir->rd_fd);
+			}
+			/* Sometimes we do another close on restore, getting EBADF.
+			 * Consider "echo 3>FILE 3>&-"
+			 * first redirect remembers "need to close 3",
+			 * and second redirect closes 3! Restore code then closes 3 again.
+			 */
+		} else {
+			/* if newfd is a script fd or saved fd, simulate EBADF */
+			if (internally_opened_fd(newfd, sqp && sqp != ERR_PTR ? *sqp : NULL)) {
+				//errno = EBADF;
+				//bb_perror_msg_and_die("can't duplicate file descriptor");
+				newfd = -1; /* same effect as code above */
+			}
+			xdup2(newfd, redir->rd_fd);
+			if (redir->rd_dup == REDIRFD_TO_FILE)
+				/* "rd_fd > FILE" */
+				close(newfd);
+			/* else: "rd_fd > rd_dup" */
+		}
+	}
+	return 0;
+}
+
+static char *find_in_path(const char *arg)
+{
+	char *ret = NULL;
+	const char *PATH = get_local_var_value("PATH");
+
+	if (!PATH)
+		return NULL;
+
+	while (1) {
+		const char *end = strchrnul(PATH, ':');
+		int sz = end - PATH; /* must be int! */
+
+		free(ret);
+		if (sz != 0) {
+			ret = xasprintf("%.*s/%s", sz, PATH, arg);
+		} else {
+			/* We have xxx::yyyy in $PATH,
+			 * it means "use current dir" */
+			ret = xstrdup(arg);
+		}
+		if (access(ret, F_OK) == 0)
+			break;
+
+		if (*end == '\0') {
+			free(ret);
+			return NULL;
+		}
+		PATH = end + 1;
+	}
+
+	return ret;
+}
+
+static const struct built_in_command *find_builtin_helper(const char *name,
+		const struct built_in_command *x,
+		const struct built_in_command *end)
+{
+	while (x != end) {
+		if (strcmp(name, x->b_cmd) != 0) {
+			x++;
+			continue;
+		}
+		debug_printf_exec("found builtin '%s'\n", name);
+		return x;
+	}
+	return NULL;
+}
+static const struct built_in_command *find_builtin1(const char *name)
+{
+	return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]);
+}
+static const struct built_in_command *find_builtin(const char *name)
+{
+	const struct built_in_command *x = find_builtin1(name);
+	if (x)
+		return x;
+	return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]);
+}
+
+#if ENABLE_HUSH_JOB && EDITING_HAS_get_exe_name
+static const char * FAST_FUNC get_builtin_name(int i)
+{
+	if (/*i >= 0 && */ i < ARRAY_SIZE(bltins1)) {
+		return bltins1[i].b_cmd;
+	}
+	i -= ARRAY_SIZE(bltins1);
+	if (i < ARRAY_SIZE(bltins2)) {
+		return bltins2[i].b_cmd;
+	}
+	return NULL;
+}
+#endif
+
+static void remove_nested_vars(void)
+{
+	struct variable *cur;
+	struct variable **cur_pp;
+
+	cur_pp = &G.top_var;
+	while ((cur = *cur_pp) != NULL) {
+		if (cur->var_nest_level <= G.var_nest_level) {
+			cur_pp = &cur->next;
+			continue;
+		}
+		/* Unexport */
+		if (cur->flg_export) {
+			debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
+			bb_unsetenv(cur->varstr);
+		}
+		/* Remove from global list */
+		*cur_pp = cur->next;
+		/* Free */
+		if (!cur->max_len) {
+			debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
+			free(cur->varstr);
+		}
+		free(cur);
+	}
+}
+
+static void enter_var_nest_level(void)
+{
+	G.var_nest_level++;
+	debug_printf_env("var_nest_level++ %u\n", G.var_nest_level);
+
+	/* Try:	f() { echo -n .; f; }; f
+	 * struct variable::var_nest_level is uint16_t,
+	 * thus limiting recursion to < 2^16.
+	 * In any case, with 8 Mbyte stack SEGV happens
+	 * not too long after 2^16 recursions anyway.
+	 */
+	if (G.var_nest_level > 0xff00)
+		bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level);
+}
+
+static void leave_var_nest_level(void)
+{
+	G.var_nest_level--;
+	debug_printf_env("var_nest_level-- %u\n", G.var_nest_level);
+	if (HUSH_DEBUG && (int)G.var_nest_level < 0)
+		bb_simple_error_msg_and_die("BUG: nesting underflow");
+
+	remove_nested_vars();
+}
+
+#if ENABLE_HUSH_FUNCTIONS
+static struct function **find_function_slot(const char *name)
+{
+	struct function *funcp;
+	struct function **funcpp = &G.top_func;
+
+	while ((funcp = *funcpp) != NULL) {
+		if (strcmp(name, funcp->name) == 0) {
+			debug_printf_exec("found function '%s'\n", name);
+			break;
+		}
+		funcpp = &funcp->next;
+	}
+	return funcpp;
+}
+
+static ALWAYS_INLINE const struct function *find_function(const char *name)
+{
+	const struct function *funcp = *find_function_slot(name);
+	return funcp;
+}
+
+/* Note: takes ownership on name ptr */
+static struct function *new_function(char *name)
+{
+	struct function **funcpp = find_function_slot(name);
+	struct function *funcp = *funcpp;
+
+	if (funcp != NULL) {
+		struct command *cmd = funcp->parent_cmd;
+		debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd);
+		if (!cmd) {
+			debug_printf_exec("freeing & replacing function '%s'\n", funcp->name);
+			free(funcp->name);
+			/* Note: if !funcp->body, do not free body_as_string!
+			 * This is a special case of "-F name body" function:
+			 * body_as_string was not malloced! */
+			if (funcp->body) {
+				free_pipe_list(funcp->body);
+# if !BB_MMU
+				free(funcp->body_as_string);
+# endif
+			}
+		} else {
+			debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name);
+			cmd->argv[0] = funcp->name;
+			cmd->group = funcp->body;
+# if !BB_MMU
+			cmd->group_as_string = funcp->body_as_string;
+# endif
+		}
+	} else {
+		debug_printf_exec("remembering new function '%s'\n", name);
+		funcp = *funcpp = xzalloc(sizeof(*funcp));
+		/*funcp->next = NULL;*/
+	}
+
+	funcp->name = name;
+	return funcp;
+}
+
+# if ENABLE_HUSH_UNSET
+static void unset_func(const char *name)
+{
+	struct function **funcpp = find_function_slot(name);
+	struct function *funcp = *funcpp;
+
+	if (funcp != NULL) {
+		debug_printf_exec("freeing function '%s'\n", funcp->name);
+		*funcpp = funcp->next;
+		/* funcp is unlinked now, deleting it.
+		 * Note: if !funcp->body, the function was created by
+		 * "-F name body", do not free ->body_as_string
+		 * and ->name as they were not malloced. */
+		if (funcp->body) {
+			free_pipe_list(funcp->body);
+			free(funcp->name);
+#  if !BB_MMU
+			free(funcp->body_as_string);
+#  endif
+		}
+		free(funcp);
+	}
+}
+# endif
+
+# if BB_MMU
+#define exec_function(to_free, funcp, argv) \
+	exec_function(funcp, argv)
+# endif
+static void exec_function(char ***to_free,
+		const struct function *funcp,
+		char **argv) NORETURN;
+static void exec_function(char ***to_free,
+		const struct function *funcp,
+		char **argv)
+{
+# if BB_MMU
+	int n;
+
+	argv[0] = G.global_argv[0];
+	G.global_argv = argv;
+	G.global_argc = n = 1 + string_array_len(argv + 1);
+
+// Example when we are here: "cmd | func"
+// func will run with saved-redirect fds open.
+// $ f() { echo /proc/self/fd/*; }
+// $ true | f
+// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3
+// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ DIR fd for glob
+// Same in script:
+// $ . ./SCRIPT
+// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3 /proc/self/fd/4
+// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ opened ./SCRIPT DIR fd for glob
+// They are CLOEXEC so external programs won't see them, but
+// for "more correctness" we might want to close those extra fds here:
+//?	close_saved_fds_and_FILE_fds();
+
+	/* "we are in a function, ok to use return" */
+	G_flag_return_in_progress = -1;
+	enter_var_nest_level();
+	IF_HUSH_LOCAL(G.func_nest_level++;)
+
+	/* On MMU, funcp->body is always non-NULL */
+	n = run_list(funcp->body);
+	_exit(n);
+# else
+//?	close_saved_fds_and_FILE_fds();
+
+//TODO: check whether "true | func_with_return" works
+
+	re_execute_shell(to_free,
+			funcp->body_as_string,
+			G.global_argv[0],
+			argv + 1,
+			NULL);
+# endif
+}
+
+static int run_function(const struct function *funcp, char **argv)
+{
+	int rc;
+	save_arg_t sv;
+	smallint sv_flg;
+
+	save_and_replace_G_args(&sv, argv);
+
+	/* "We are in function, ok to use return" */
+	sv_flg = G_flag_return_in_progress;
+	G_flag_return_in_progress = -1;
+
+	/* Make "local" variables properly shadow previous ones */
+	IF_HUSH_LOCAL(enter_var_nest_level();)
+	IF_HUSH_LOCAL(G.func_nest_level++;)
+
+	/* On MMU, funcp->body is always non-NULL */
+# if !BB_MMU
+	if (!funcp->body) {
+		/* Function defined by -F */
+		parse_and_run_string(funcp->body_as_string);
+		rc = G.last_exitcode;
+	} else
+# endif
+	{
+		rc = run_list(funcp->body);
+	}
+
+	IF_HUSH_LOCAL(G.func_nest_level--;)
+	IF_HUSH_LOCAL(leave_var_nest_level();)
+
+	G_flag_return_in_progress = sv_flg;
+# if ENABLE_HUSH_TRAP
+	debug_printf_exec("G.return_exitcode=-1\n");
+	G.return_exitcode = -1; /* invalidate stashed return value */
+# endif
+
+	restore_G_args(&sv, argv);
+
+	return rc;
+}
+#endif /* ENABLE_HUSH_FUNCTIONS */
+
+
+#if BB_MMU
+#define exec_builtin(to_free, x, argv) \
+	exec_builtin(x, argv)
+#else
+#define exec_builtin(to_free, x, argv) \
+	exec_builtin(to_free, argv)
+#endif
+static void exec_builtin(char ***to_free,
+		const struct built_in_command *x,
+		char **argv) NORETURN;
+static void exec_builtin(char ***to_free,
+		const struct built_in_command *x,
+		char **argv)
+{
+#if BB_MMU
+	int rcode;
+//?	close_saved_fds_and_FILE_fds();
+	rcode = x->b_function(argv);
+	fflush_all();
+	_exit(rcode);
+#else
+	fflush_all();
+	/* On NOMMU, we must never block!
+	 * Example: { sleep 99 | read line; } & echo Ok
+	 */
+	re_execute_shell(to_free,
+			argv[0],
+			G.global_argv[0],
+			G.global_argv + 1,
+			argv);
+#endif
+}
+
+
+static void execvp_or_die(char **argv) NORETURN;
+static void execvp_or_die(char **argv)
+{
+	int e;
+	debug_printf_exec("execing '%s'\n", argv[0]);
+	/* Don't propagate SIG_IGN to the child */
+	if (SPECIAL_JOBSTOP_SIGS != 0)
+		switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
+	execvp(argv[0], argv);
+	e = 2;
+	if (errno == EACCES) e = 126;
+	if (errno == ENOENT) e = 127;
+	bb_perror_msg("can't execute '%s'", argv[0]);
+	_exit(e);
+}
+
+#if ENABLE_HUSH_MODE_X
+static void x_mode_print_optionally_squoted(const char *str)
+{
+	unsigned len;
+	const char *cp;
+
+	cp = str;
+
+	/* the set of chars which-cause-string-to-be-squoted mimics bash */
+	/* test a char with: bash -c 'set -x; echo "CH"' */
+	if (str[strcspn(str, "\\\"'`$(){}[]<>;#&|~*?!^"
+			" " "\001\002\003\004\005\006\007"
+			"\010\011\012\013\014\015\016\017"
+			"\020\021\022\023\024\025\026\027"
+			"\030\031\032\033\034\035\036\037"
+			)
+		] == '\0'
+	) {
+		/* string has no special chars */
+		x_mode_addstr(str);
+		return;
+	}
+
+	cp = str;
+	for (;;) {
+		/* print '....' up to EOL or first squote */
+		len = (int)(strchrnul(cp, '\'') - cp);
+		if (len != 0) {
+			x_mode_addchr('\'');
+			x_mode_addblock(cp, len);
+			x_mode_addchr('\'');
+			cp += len;
+		}
+		if (*cp == '\0')
+			break;
+		/* string contains squote(s), print them as \' */
+		x_mode_addchr('\\');
+		x_mode_addchr('\'');
+		cp++;
+	}
+}
+static void dump_cmd_in_x_mode(char **argv)
+{
+	if (G_x_mode && argv) {
+		unsigned n;
+
+		/* "+[+++...][ cmd...]\n\0" */
+		x_mode_prefix();
+		n = 0;
+		while (argv[n]) {
+			x_mode_addchr(' ');
+			if (argv[n][0] == '\0') {
+				x_mode_addchr('\'');
+				x_mode_addchr('\'');
+			} else {
+				x_mode_print_optionally_squoted(argv[n]);
+			}
+			n++;
+		}
+		x_mode_flush();
+	}
+}
+#else
+# define dump_cmd_in_x_mode(argv) ((void)0)
+#endif
+
+#if ENABLE_HUSH_COMMAND
+static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *explanation)
+{
+	char *to_free;
+
+	if (!opt_vV)
+		return;
+
+	to_free = NULL;
+	if (!explanation) {
+		char *path = getenv("PATH");
+		explanation = to_free = find_executable(cmd, &path); /* path == NULL is ok */
+		if (!explanation)
+			_exit(1); /* PROG was not found */
+		if (opt_vV != 'V')
+			cmd = to_free; /* -v PROG prints "/path/to/PROG" */
+	}
+	printf((opt_vV == 'V') ? "%s is %s\n" : "%s\n", cmd, explanation);
+	free(to_free);
+	fflush_all();
+	_exit(0);
+}
+#else
+# define if_command_vV_print_and_exit(a,b,c) ((void)0)
+#endif
+
+#if BB_MMU
+#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
+	pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
+#define pseudo_exec(nommu_save, command, argv_expanded) \
+	pseudo_exec(command, argv_expanded)
+#endif
+
+/* Called after [v]fork() in run_pipe, or from builtin_exec.
+ * Never returns.
+ * Don't exit() here.  If you don't exec, use _exit instead.
+ * The at_exit handlers apparently confuse the calling process,
+ * in particular stdin handling. Not sure why? -- because of vfork! (vda)
+ */
+static void pseudo_exec_argv(nommu_save_t *nommu_save,
+		char **argv, int assignment_cnt,
+		char **argv_expanded) NORETURN;
+static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
+		char **argv, int assignment_cnt,
+		char **argv_expanded)
+{
+	const struct built_in_command *x;
+	struct variable **sv_shadowed;
+	char **new_env;
+	IF_HUSH_COMMAND(char opt_vV = 0;)
+	IF_HUSH_FUNCTIONS(const struct function *funcp;)
+
+	new_env = expand_assignments(argv, assignment_cnt);
+	dump_cmd_in_x_mode(new_env);
+
+	if (!argv[assignment_cnt]) {
+		/* Case when we are here: ... | var=val | ...
+		 * (note that we do not exit early, i.e., do not optimize out
+		 * expand_assignments(): think about ... | var=`sleep 1` | ...
+		 */
+		free_strings(new_env);
+		_exit(EXIT_SUCCESS);
+	}
+
+	sv_shadowed = G.shadowed_vars_pp;
+#if BB_MMU
+	G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */
+#else
+	G.shadowed_vars_pp = &nommu_save->old_vars;
+	G.var_nest_level++;
+#endif
+	set_vars_and_save_old(new_env);
+	G.shadowed_vars_pp = sv_shadowed;
+
+	if (argv_expanded) {
+		argv = argv_expanded;
+	} else {
+		argv = expand_strvec_to_strvec(argv + assignment_cnt);
+#if !BB_MMU
+		nommu_save->argv = argv;
+#endif
+	}
+	dump_cmd_in_x_mode(argv);
+
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+	if (strchr(argv[0], '/') != NULL)
+		goto skip;
+#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+	/* Check if the command matches any functions (this goes before bltins) */
+	funcp = find_function(argv[0]);
+	if (funcp)
+		exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
+#endif
+
+#if ENABLE_HUSH_COMMAND
+	/* "command BAR": run BAR without looking it up among functions
+	 * "command -v BAR": print "BAR" or "/path/to/BAR"; or exit 1
+	 * "command -V BAR": print "BAR is {a function,a shell builtin,/path/to/BAR}"
+	 */
+	while (strcmp(argv[0], "command") == 0 && argv[1]) {
+		char *p;
+
+		argv++;
+		p = *argv;
+		if (p[0] != '-' || !p[1])
+			continue; /* bash allows "command command command [-OPT] BAR" */
+
+		for (;;) {
+			p++;
+			switch (*p) {
+			case '\0':
+				argv++;
+				p = *argv;
+				if (p[0] != '-' || !p[1])
+					goto after_opts;
+				continue; /* next arg is also -opts, process it too */
+			case 'v':
+			case 'V':
+				opt_vV = *p;
+				continue;
+			default:
+				bb_error_msg_and_die("%s: %s: invalid option", "command", argv[0]);
+			}
+		}
+	}
+ after_opts:
+# if ENABLE_HUSH_FUNCTIONS
+	if (opt_vV && find_function(argv[0]))
+		if_command_vV_print_and_exit(opt_vV, argv[0], "a function");
+# endif
+#endif
+
+	/* Check if the command matches any of the builtins.
+	 * Depending on context, this might be redundant.  But it's
+	 * easier to waste a few CPU cycles than it is to figure out
+	 * if this is one of those cases.
+	 */
+	/* Why "BB_MMU ? :" difference in logic? -
+	 * On NOMMU, it is more expensive to re-execute shell
+	 * just in order to run echo or test builtin.
+	 * It's better to skip it here and run corresponding
+	 * non-builtin later. */
+	x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
+	if (x) {
+		if_command_vV_print_and_exit(opt_vV, argv[0], "a shell builtin");
+		exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
+	}
+
+#if ENABLE_FEATURE_SH_STANDALONE
+	/* Check if the command matches any busybox applets */
+	{
+		int a = find_applet_by_name(argv[0]);
+		if (a >= 0) {
+			if_command_vV_print_and_exit(opt_vV, argv[0], "an applet");
+# if BB_MMU /* see above why on NOMMU it is not allowed */
+			if (APPLET_IS_NOEXEC(a)) {
+				/* Do not leak open fds from opened script files etc.
+				 * Testcase: interactive "ls -l /proc/self/fd"
+				 * should not show tty fd open.
+				 */
+				close_saved_fds_and_FILE_fds();
+//FIXME: should also close saved redir fds
+//This casuses test failures in
+//redir_children_should_not_see_saved_fd_2.tests
+//redir_children_should_not_see_saved_fd_3.tests
+//if you replace "busybox find" with just "find" in them
+				/* Without this, "rm -i FILE" can't be ^C'ed: */
+				switch_off_special_sigs(G.special_sig_mask);
+				debug_printf_exec("running applet '%s'\n", argv[0]);
+				run_noexec_applet_and_exit(a, argv[0], argv);
+			}
+# endif
+			/* Re-exec ourselves */
+			debug_printf_exec("re-execing applet '%s'\n", argv[0]);
+			/* Don't propagate SIG_IGN to the child */
+			if (SPECIAL_JOBSTOP_SIGS != 0)
+				switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
+			execv(bb_busybox_exec_path, argv);
+			/* If they called chroot or otherwise made the binary no longer
+			 * executable, fall through */
+		}
+	}
+#endif
+
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+ skip:
+#endif
+	if_command_vV_print_and_exit(opt_vV, argv[0], NULL);
+	execvp_or_die(argv);
+}
+
+/* Called after [v]fork() in run_pipe
+ */
+static void pseudo_exec(nommu_save_t *nommu_save,
+		struct command *command,
+		char **argv_expanded) NORETURN;
+static void pseudo_exec(nommu_save_t *nommu_save,
+		struct command *command,
+		char **argv_expanded)
+{
+#if ENABLE_HUSH_FUNCTIONS
+	if (command->cmd_type == CMD_FUNCDEF) {
+		/* Ignore funcdefs in pipes:
+		 * true | f() { cmd }
+		 */
+		_exit(0);
+	}
+#endif
+
+	if (command->argv) {
+		pseudo_exec_argv(nommu_save, command->argv,
+				command->assignment_cnt, argv_expanded);
+	}
+
+	if (command->group) {
+		/* Cases when we are here:
+		 * ( list )
+		 * { list } &
+		 * ... | ( list ) | ...
+		 * ... | { list } | ...
+		 */
+#if BB_MMU
+		int rcode;
+		debug_printf_exec("pseudo_exec: run_list\n");
+		reset_traps_to_defaults();
+		rcode = run_list(command->group);
+		/* OK to leak memory by not calling free_pipe_list,
+		 * since this process is about to exit */
+		_exit(rcode);
+#else
+		re_execute_shell(&nommu_save->argv_from_re_execing,
+				command->group_as_string,
+				G.global_argv[0],
+				G.global_argv + 1,
+				NULL);
+#endif
+	}
+
+	/* Case when we are here: ... | >file */
+	debug_printf_exec("pseudo_exec'ed null command\n");
+	_exit(EXIT_SUCCESS);
+}
+
+#if ENABLE_HUSH_JOB
+static const char *get_cmdtext(struct pipe *pi)
+{
+	char **argv;
+	char *p;
+	int len;
+
+	/* This is subtle. ->cmdtext is created only on first backgrounding.
+	 * (Think "cat, <ctrl-z>, fg, <ctrl-z>, fg, <ctrl-z>...." here...)
+	 * On subsequent bg argv is trashed, but we won't use it */
+	if (pi->cmdtext)
+		return pi->cmdtext;
+
+	argv = pi->cmds[0].argv;
+	if (!argv) {
+		pi->cmdtext = xzalloc(1);
+		return pi->cmdtext;
+	}
+	len = 0;
+	do {
+		len += strlen(*argv) + 1;
+	} while (*++argv);
+	p = xmalloc(len);
+	pi->cmdtext = p;
+	argv = pi->cmds[0].argv;
+	do {
+		p = stpcpy(p, *argv);
+		*p++ = ' ';
+	} while (*++argv);
+	p[-1] = '\0';
+	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;
+	while ((job = *jobp) != NULL) {
+		if (job->jobid > i)
+			i = job->jobid;
+		jobp = &job->next;
+	}
+	pi->jobid = i + 1;
+
+	/* 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 */
+	for (i = 0; i < pi->num_cmds; i++) {
+		job->cmds[i].pid = pi->cmds[i].pid;
+		/* all other fields are not used and stay zero */
+	}
+	job->cmdtext = xstrdup(get_cmdtext(pi));
+
+	if (G_interactive_fd)
+		printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext);
+	G.last_jobid = job->jobid;
+}
+#endif /* JOB */
+
+static int job_exited_or_stopped(struct pipe *pi)
+{
+	int rcode, i;
+
+	if (pi->alive_cmds != pi->stopped_cmds)
+		return -1;
+
+	/* All processes in fg pipe have exited or stopped */
+	rcode = 0;
+	i = pi->num_cmds;
+	while (--i >= 0) {
+		rcode = pi->cmds[i].cmd_exitcode;
+		/* usually last process gives overall exitstatus,
+		 * but with "set -o pipefail", last *failed* process does */
+		if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
+			break;
+	}
+	IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+	return rcode;
+}
+
+static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
+{
+#if ENABLE_HUSH_JOB
+	struct pipe *pi;
+#endif
+	int i, dead;
+
+	dead = WIFEXITED(status) || WIFSIGNALED(status);
+
+#if DEBUG_JOBS
+	if (WIFSTOPPED(status))
+		debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
+				childpid, WSTOPSIG(status), WEXITSTATUS(status));
+	if (WIFSIGNALED(status))
+		debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n",
+				childpid, WTERMSIG(status), WEXITSTATUS(status));
+	if (WIFEXITED(status))
+		debug_printf_jobs("pid %d exited, exitcode %d\n",
+				childpid, WEXITSTATUS(status));
+#endif
+	/* Were we asked to wait for a fg pipe? */
+	if (fg_pipe) {
+		i = fg_pipe->num_cmds;
+
+		while (--i >= 0) {
+			int rcode;
+
+			debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
+			if (fg_pipe->cmds[i].pid != childpid)
+				continue;
+			if (dead) {
+				int ex;
+				fg_pipe->cmds[i].pid = 0;
+				fg_pipe->alive_cmds--;
+				ex = WEXITSTATUS(status);
+				/* bash prints killer signal's name for *last*
+				 * process in pipe (prints just newline for SIGINT/SIGPIPE).
+				 * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
+				 */
+				if (WIFSIGNALED(status)) {
+					int sig = WTERMSIG(status);
+#if ENABLE_HUSH_JOB
+					if (G.run_list_level == 1
+					/* ^^^^^ Do not print in nested contexts, example:
+					 * echo `sleep 1; sh -c 'kill -9 $$'` - prints "137", NOT "Killed 137"
+					 */
+					 && i == fg_pipe->num_cmds-1
+					) {
+						/* strsignal() is for bash compat. ~600 bloat versus bbox's get_signame() */
+						puts(sig == SIGINT || sig == SIGPIPE ? "" : strsignal(sig));
+					}
+#endif
+					/* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
+					/* MIPS has 128 sigs (1..128), if sig==128,
+					 * 128 + sig would result in exitcode 256 -> 0!
+					 */
+					ex = 128 | sig;
+				}
+				fg_pipe->cmds[i].cmd_exitcode = ex;
+			} else {
+				fg_pipe->stopped_cmds++;
+			}
+			debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
+					fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
+			rcode = job_exited_or_stopped(fg_pipe);
+			if (rcode >= 0) {
+/* Note: *non-interactive* bash does not continue if all processes in fg pipe
+ * are stopped. Testcase: "cat | cat" in a script (not on command line!)
+ * and "killall -STOP cat" */
+				if (G_interactive_fd) {
+#if ENABLE_HUSH_JOB
+					if (fg_pipe->alive_cmds != 0)
+						insert_job_into_table(fg_pipe);
+#endif
+					return rcode;
+				}
+				if (fg_pipe->alive_cmds == 0)
+					return rcode;
+			}
+			/* There are still running processes in the fg_pipe */
+			return -1;
+		}
+		/* It wasn't in fg_pipe, look for process in bg pipes */
+	}
+
+#if ENABLE_HUSH_JOB
+	/* We were asked to wait for bg or orphaned children */
+	/* No need to remember exitcode in this case */
+	for (pi = G.job_list; pi; pi = pi->next) {
+		for (i = 0; i < pi->num_cmds; i++) {
+			if (pi->cmds[i].pid == childpid)
+				goto found_pi_and_prognum;
+		}
+	}
+	/* Happens when shell is used as init process (init=/bin/sh) */
+	debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
+	return -1; /* this wasn't a process from fg_pipe */
+
+ found_pi_and_prognum:
+	if (dead) {
+		/* child exited */
+		int rcode = WEXITSTATUS(status);
+		if (WIFSIGNALED(status))
+			/* NB: not 128 + sig, MIPS has sig 128 */
+			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 ENABLE_HUSH_BASH_COMPAT
+			G.dead_job_exitcode = job_exited_or_stopped(pi);
+# endif
+			if (G_interactive_fd) {
+				printf(JOB_STATUS_FORMAT, pi->jobid,
+						"Done", pi->cmdtext);
+				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 */
+		pi->stopped_cmds++;
+	}
+#endif
+	return -1; /* this wasn't a process from fg_pipe */
+}
+
+/* Check to see if any processes have exited -- if they have,
+ * figure out why and see if a job has completed.
+ *
+ * If non-NULL fg_pipe: wait for its completion or stop.
+ * Return its exitcode or zero if stopped.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid != 0):
+ * waitpid(WNOHANG), if waitfor_pid exits or stops, return exitcode+1,
+ * else return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid == 0),
+ * return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
+ */
+static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
+{
+	int attributes;
+	int status;
+	int rcode = 0;
+
+	debug_printf_jobs("checkjobs %p\n", fg_pipe);
+
+	attributes = WUNTRACED;
+	if (fg_pipe == NULL)
+		attributes |= WNOHANG;
+
+	errno = 0;
+#if ENABLE_HUSH_FAST
+	if (G.handled_SIGCHLD == G.count_SIGCHLD) {
+//bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p",
+//getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe);
+		/* There was neither fork nor SIGCHLD since last waitpid */
+		/* Avoid doing waitpid syscall if possible */
+		if (!G.we_have_children) {
+			errno = ECHILD;
+			return -1;
+		}
+		if (fg_pipe == NULL) { /* is WNOHANG set? */
+			/* We have children, but they did not exit
+			 * or stop yet (we saw no SIGCHLD) */
+			return 0;
+		}
+		/* else: !WNOHANG, waitpid will block, can't short-circuit */
+	}
+#endif
+
+/* Do we do this right?
+ * bash-3.00# sleep 20 | false
+ * <ctrl-Z pressed>
+ * [3]+  Stopped          sleep 20 | false
+ * bash-3.00# echo $?
+ * 1   <========== bg pipe is not fully done, but exitcode is already known!
+ * [hush 1.14.0: yes we do it right]
+ */
+	while (1) {
+		pid_t childpid;
+#if ENABLE_HUSH_FAST
+		int i;
+		i = G.count_SIGCHLD;
+#endif
+		childpid = waitpid(-1, &status, attributes);
+		if (childpid <= 0) {
+			if (childpid && errno != ECHILD)
+				bb_simple_perror_msg("waitpid");
+#if ENABLE_HUSH_FAST
+			else { /* Until next SIGCHLD, waitpid's are useless */
+				G.we_have_children = (childpid == 0);
+				G.handled_SIGCHLD = i;
+//bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+			}
+#endif
+			/* ECHILD (no children), or 0 (no change in children status) */
+			rcode = childpid;
+			break;
+		}
+		rcode = process_wait_result(fg_pipe, childpid, status);
+		if (rcode >= 0) {
+			/* fg_pipe exited or stopped */
+			break;
+		}
+		if (childpid == waitfor_pid) { /* "wait PID" */
+			debug_printf_exec("childpid==waitfor_pid:%d status:0x%08x\n", childpid, status);
+			rcode = WEXITSTATUS(status);
+			if (WIFSIGNALED(status))
+				rcode = 128 | WTERMSIG(status);
+			if (WIFSTOPPED(status))
+				/* bash: "cmd & wait $!" and cmd stops: $? = 128 | stopsig */
+				rcode = 128 | WSTOPSIG(status);
+			rcode++;
+			break; /* "wait PID" called us, give it exitcode+1 */
+		}
+#if ENABLE_HUSH_BASH_COMPAT
+		if (-1 == waitfor_pid /* "wait -n" (wait for any one job) */
+		 && G.dead_job_exitcode >= 0 /* some job did finish */
+		) {
+			debug_printf_exec("waitfor_pid:-1\n");
+			rcode = G.dead_job_exitcode + 1;
+			break;
+		}
+#endif
+		/* This wasn't one of our processes, or */
+		/* fg_pipe still has running processes, do waitpid again */
+	} /* while (waitpid succeeds)... */
+
+	return rcode;
+}
+
+#if ENABLE_HUSH_JOB
+static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
+{
+	pid_t p;
+	int rcode = checkjobs(fg_pipe, 0 /*(no pid to wait for)*/);
+	if (G_saved_tty_pgrp) {
+		/* Job finished, move the shell to the foreground */
+		p = getpgrp(); /* our process group id */
+		debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p);
+		tcsetpgrp(G_interactive_fd, p);
+	}
+	return rcode;
+}
+#endif
+
+/* Start all the jobs, but don't wait for anything to finish.
+ * See checkjobs().
+ *
+ * Return code is normally -1, when the caller has to wait for children
+ * to finish to determine the exit status of the pipe.  If the pipe
+ * is a simple builtin command, however, the action is done by the
+ * time run_pipe returns, and the exit code is provided as the
+ * return value.
+ *
+ * Returns -1 only if started some children. IOW: we have to
+ * mask out retvals of builtins etc with 0xff!
+ *
+ * The only case when we do not need to [v]fork is when the pipe
+ * is single, non-backgrounded, non-subshell command. Examples:
+ * cmd ; ...   { list } ; ...
+ * cmd && ...  { list } && ...
+ * cmd || ...  { list } || ...
+ * If it is, then we can run cmd as a builtin, NOFORK,
+ * or (if SH_STANDALONE) an applet, and we can run the { list }
+ * with run_list. If it isn't one of these, we fork and exec cmd.
+ *
+ * Cases when we must fork:
+ * non-single:   cmd | cmd
+ * backgrounded: cmd &     { list } &
+ * subshell:     ( list ) [&]
+ */
+#if !ENABLE_HUSH_MODE_X
+#define redirect_and_varexp_helper(command, sqp, argv_expanded) \
+	redirect_and_varexp_helper(command, sqp)
+#endif
+static int redirect_and_varexp_helper(
+		struct command *command,
+		struct squirrel **sqp,
+		char **argv_expanded)
+{
+	/* Assignments occur before redirects. Try:
+	 * a=`sleep 1` sleep 2 3>/qwe/rty
+	 */
+
+	char **new_env = expand_assignments(command->argv, command->assignment_cnt);
+	dump_cmd_in_x_mode(new_env);
+	dump_cmd_in_x_mode(argv_expanded);
+	/* this takes ownership of new_env[i] elements, and frees new_env: */
+	set_vars_and_save_old(new_env);
+
+	return setup_redirects(command, sqp);
+}
+static NOINLINE int run_pipe(struct pipe *pi)
+{
+	static const char *const null_ptr = NULL;
+
+	int cmd_no;
+	int next_infd;
+	struct command *command;
+	char **argv_expanded;
+	char **argv;
+	struct squirrel *squirrel = NULL;
+	int rcode;
+
+	debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
+	debug_enter();
+
+	/* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*"
+	 * Result should be 3 lines: q w e, qwe, q w e
+	 */
+	if (G.ifs_whitespace != G.ifs)
+		free(G.ifs_whitespace);
+	G.ifs = get_local_var_value("IFS");
+	if (G.ifs) {
+		char *p;
+		G.ifs_whitespace = (char*)G.ifs;
+		p = skip_whitespace(G.ifs);
+		if (*p) {
+			/* Not all $IFS is whitespace */
+			char *d;
+			int len = p - G.ifs;
+			p = skip_non_whitespace(p);
+			G.ifs_whitespace = xmalloc(len + strlen(p) + 1); /* can overestimate */
+			d = mempcpy(G.ifs_whitespace, G.ifs, len);
+			while (*p) {
+				if (isspace(*p))
+					*d++ = *p;
+				p++;
+			}
+			*d = '\0';
+		}
+	} else {
+		G.ifs = defifs;
+		G.ifs_whitespace = (char*)G.ifs;
+	}
+
+	IF_HUSH_JOB(pi->pgrp = -1;)
+	pi->stopped_cmds = 0;
+	command = &pi->cmds[0];
+	argv_expanded = NULL;
+
+	if (pi->num_cmds != 1
+	 || pi->followup == PIPE_BG
+	 || command->cmd_type == CMD_SUBSHELL
+	) {
+		goto must_fork;
+	}
+
+	pi->alive_cmds = 1;
+
+	debug_printf_exec(": group:%p argv:'%s'\n",
+		command->group, command->argv ? command->argv[0] : "NONE");
+
+	if (command->group) {
+#if ENABLE_HUSH_FUNCTIONS
+		if (command->cmd_type == CMD_FUNCDEF) {
+			/* "executing" func () { list } */
+			struct function *funcp;
+
+			funcp = new_function(command->argv[0]);
+			/* funcp->name is already set to argv[0] */
+			funcp->body = command->group;
+# if !BB_MMU
+			funcp->body_as_string = command->group_as_string;
+			command->group_as_string = NULL;
+# endif
+			command->group = NULL;
+			command->argv[0] = NULL;
+			debug_printf_exec("cmd %p has child func at %p\n", command, funcp);
+			funcp->parent_cmd = command;
+			command->child_func = funcp;
+
+			debug_printf_exec("run_pipe: return EXIT_SUCCESS\n");
+			debug_leave();
+			return EXIT_SUCCESS;
+		}
+#endif
+		/* { list } */
+		debug_printf_exec("non-subshell group\n");
+		rcode = 1; /* exitcode if redir failed */
+		if (setup_redirects(command, &squirrel) == 0) {
+			debug_printf_exec(": run_list\n");
+//FIXME: we need to pass squirrel down into run_list()
+//for SH_STANDALONE case, or else this construct:
+// { find /proc/self/fd; true; } >FILE; cmd2
+//has no way of closing saved fd#1 for "find",
+//and in SH_STANDALONE mode, "find" is not execed,
+//therefore CLOEXEC on saved fd does not help.
+			rcode = run_list(command->group) & 0xff;
+		}
+		restore_redirects(squirrel);
+		IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+		debug_leave();
+		debug_printf_exec("run_pipe: return %d\n", rcode);
+		return rcode;
+	}
+
+	argv = command->argv ? command->argv : (char **) &null_ptr;
+	{
+		const struct built_in_command *x;
+		IF_HUSH_FUNCTIONS(const struct function *funcp;)
+		IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };)
+		struct variable **sv_shadowed;
+		struct variable *old_vars;
+
+#if ENABLE_HUSH_LINENO_VAR
+		G.execute_lineno = command->lineno;
+#endif
+
+		if (argv[command->assignment_cnt] == NULL) {
+			/* Assignments, but no command.
+			 * Ensure redirects take effect (that is, create files).
+			 * Try "a=t >file"
+			 */
+			unsigned i;
+			G.expand_exitcode = 0;
+ only_assignments:
+			rcode = setup_redirects(command, &squirrel);
+			restore_redirects(squirrel);
+
+			/* Set shell variables */
+			i = 0;
+			while (i < command->assignment_cnt) {
+				char *p = expand_string_to_string(argv[i],
+						EXP_FLAG_ESC_GLOB_CHARS,
+						/*unbackslash:*/ 1
+				);
+#if ENABLE_HUSH_MODE_X
+				if (G_x_mode) {
+					char *eq;
+					if (i == 0)
+						x_mode_prefix();
+					x_mode_addchr(' ');
+					eq = strchrnul(p, '=');
+					if (*eq) eq++;
+					x_mode_addblock(p, (eq - p));
+					x_mode_print_optionally_squoted(eq);
+					x_mode_flush();
+				}
+#endif
+				debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
+				if (set_local_var(p, /*flag:*/ 0)) {
+					/* assignment to readonly var / putenv error? */
+					rcode = 1;
+				}
+				i++;
+			}
+			/* Redirect error sets $? to 1. Otherwise,
+			 * if evaluating assignment value set $?, retain it.
+			 * Else, clear $?:
+			 *  false; q=`exit 2`; echo $? - should print 2
+			 *  false; x=1; echo $? - should print 0
+			 * Because of the 2nd case, we can't just use G.last_exitcode.
+			 */
+			if (rcode == 0)
+				rcode = G.expand_exitcode;
+			IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+			debug_leave();
+			debug_printf_exec("run_pipe: return %d\n", rcode);
+			return rcode;
+		}
+
+		/* Expand the rest into (possibly) many strings each */
+#if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
+		if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB)
+			argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
+		else
+#endif
+#if defined(CMD_SINGLEWORD_NOGLOB)
+		if (command->cmd_type == CMD_SINGLEWORD_NOGLOB)
+			argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
+		else
+#endif
+			argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
+
+		/* If someone gives us an empty string: `cmd with empty output` */
+		if (!argv_expanded[0]) {
+			free(argv_expanded);
+			/* `false` still has to set exitcode 1 */
+			G.expand_exitcode = G.last_exitcode;
+			goto only_assignments;
+		}
+
+		old_vars = NULL;
+		sv_shadowed = G.shadowed_vars_pp;
+
+		/* Check if argv[0] matches any functions (this goes before bltins) */
+		IF_HUSH_FUNCTIONS(funcp = find_function(argv_expanded[0]);)
+		IF_HUSH_FUNCTIONS(x = NULL;)
+		IF_HUSH_FUNCTIONS(if (!funcp))
+			x = find_builtin(argv_expanded[0]);
+		if (x || funcp) {
+			if (x && x->b_function == builtin_exec && argv_expanded[1] == NULL) {
+				debug_printf("exec with redirects only\n");
+				/*
+				 * Variable assignments are executed, but then "forgotten":
+				 *  a=`sleep 1;echo A` exec 3>&-; echo $a
+				 * sleeps, but prints nothing.
+				 */
+				enter_var_nest_level();
+				G.shadowed_vars_pp = &old_vars;
+				rcode = redirect_and_varexp_helper(command,
+					/*squirrel:*/ ERR_PTR,
+					argv_expanded
+				);
+				G.shadowed_vars_pp = sv_shadowed;
+				/* rcode=1 can be if redir file can't be opened */
+
+				goto clean_up_and_ret1;
+			}
+
+			/* Bump var nesting, or this will leak exported $a:
+			 * a=b true; env | grep ^a=
+			 */
+			enter_var_nest_level();
+			/* Collect all variables "shadowed" by helper
+			 * (IOW: old vars overridden by "var1=val1 var2=val2 cmd..." syntax)
+			 * into old_vars list:
+			 */
+			G.shadowed_vars_pp = &old_vars;
+			rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded);
+			if (rcode == 0) {
+				if (!funcp) {
+					/* Do not collect *to old_vars list* vars shadowed
+					 * by e.g. "local VAR" builtin (collect them
+					 * in the previously nested list instead):
+					 * don't want them to be restored immediately
+					 * after "local" completes.
+					 */
+					G.shadowed_vars_pp = sv_shadowed;
+
+					debug_printf_exec(": builtin '%s' '%s'...\n",
+						x->b_cmd, argv_expanded[1]);
+					fflush_all();
+					rcode = x->b_function(argv_expanded) & 0xff;
+					fflush_all();
+				}
+#if ENABLE_HUSH_FUNCTIONS
+				else {
+					debug_printf_exec(": function '%s' '%s'...\n",
+						funcp->name, argv_expanded[1]);
+					rcode = run_function(funcp, argv_expanded) & 0xff;
+					/*
+					 * But do collect *to old_vars list* vars shadowed
+					 * within function execution. To that end, restore
+					 * this pointer _after_ function run:
+					 */
+					G.shadowed_vars_pp = sv_shadowed;
+				}
+#endif
+			}
+		} else
+		if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) {
+			int n = find_applet_by_name(argv_expanded[0]);
+			if (n < 0 || !APPLET_IS_NOFORK(n))
+				goto must_fork;
+
+			enter_var_nest_level();
+			/* Collect all variables "shadowed" by helper into old_vars list */
+			G.shadowed_vars_pp = &old_vars;
+			rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded);
+			G.shadowed_vars_pp = sv_shadowed;
+
+			if (rcode == 0) {
+				debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
+					argv_expanded[0], argv_expanded[1]);
+				/*
+				 * Note: signals (^C) can't interrupt here.
+				 * We remember them and they will be acted upon
+				 * after applet returns.
+				 * This makes applets which can run for a long time
+				 * and/or wait for user input ineligible for NOFORK:
+				 * for example, "yes" or "rm" (rm -i waits for input).
+				 */
+				rcode = run_nofork_applet(n, argv_expanded);
+			}
+		} else
+			goto must_fork;
+
+		restore_redirects(squirrel);
+ clean_up_and_ret1:
+		leave_var_nest_level();
+		add_vars(old_vars);
+
+		/*
+		 * Try "usleep 99999999" + ^C + "echo $?"
+		 * with FEATURE_SH_NOFORK=y.
+		 */
+		if (!funcp) {
+			/* It was builtin or nofork.
+			 * if this would be a real fork/execed program,
+			 * it should have died if a fatal sig was received.
+			 * But OTOH, there was no separate process,
+			 * the sig was sent to _shell_, not to non-existing
+			 * child.
+			 * Let's just handle ^C only, this one is obvious:
+			 * we aren't ok with exitcode 0 when ^C was pressed
+			 * during builtin/nofork.
+			 */
+			if (sigismember(&G.pending_set, SIGINT))
+				rcode = 128 | SIGINT;
+		}
+		free(argv_expanded);
+		IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+		debug_leave();
+		debug_printf_exec("run_pipe return %d\n", rcode);
+		return rcode;
+	}
+
+ must_fork:
+	/* NB: argv_expanded may already be created, and that
+	 * might include `cmd` runs! Do not rerun it! We *must*
+	 * use argv_expanded if it's non-NULL */
+
+	/* Going to fork a child per each pipe member */
+	pi->alive_cmds = 0;
+	next_infd = 0;
+
+	cmd_no = 0;
+	while (cmd_no < pi->num_cmds) {
+		struct fd_pair pipefds;
+#if !BB_MMU
+		int sv_var_nest_level = G.var_nest_level;
+		volatile nommu_save_t nommu_save;
+		nommu_save.old_vars = NULL;
+		nommu_save.argv = NULL;
+		nommu_save.argv_from_re_execing = NULL;
+#endif
+		command = &pi->cmds[cmd_no];
+		cmd_no++;
+		if (command->argv) {
+			debug_printf_exec(": pipe member '%s' '%s'...\n",
+					command->argv[0], command->argv[1]);
+		} else {
+			debug_printf_exec(": pipe member with no argv\n");
+		}
+
+		/* pipes are inserted between pairs of commands */
+		pipefds.rd = 0;
+		pipefds.wr = 1;
+		if (cmd_no < pi->num_cmds)
+			xpiped_pair(pipefds);
+
+#if ENABLE_HUSH_LINENO_VAR
+		G.execute_lineno = command->lineno;
+#endif
+
+		command->pid = BB_MMU ? fork() : vfork();
+		if (!command->pid) { /* child */
+#if ENABLE_HUSH_JOB
+			disable_restore_tty_pgrp_on_exit();
+			CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
+
+			/* Every child adds itself to new process group
+			 * with pgid == pid_of_first_child_in_pipe */
+			if (G.run_list_level == 1 && G_interactive_fd) {
+				pid_t pgrp;
+				pgrp = pi->pgrp;
+				if (pgrp < 0) /* true for 1st process only */
+					pgrp = getpid();
+				if (setpgid(0, pgrp) == 0
+				 && pi->followup != PIPE_BG
+				 && G_saved_tty_pgrp /* we have ctty */
+				) {
+					/* We do it in *every* child, not just first,
+					 * to avoid races */
+					tcsetpgrp(G_interactive_fd, pgrp);
+				}
+			}
+#endif
+			if (pi->alive_cmds == 0 && pi->followup == PIPE_BG) {
+				/* 1st cmd in backgrounded pipe
+				 * should have its stdin /dev/null'ed */
+				close(0);
+				if (open(bb_dev_null, O_RDONLY))
+					xopen("/", O_RDONLY);
+			} else {
+				xmove_fd(next_infd, 0);
+			}
+			xmove_fd(pipefds.wr, 1);
+			if (pipefds.rd > 1)
+				close(pipefds.rd);
+			/* Like bash, explicit redirects override pipes,
+			 * and the pipe fd (fd#1) is available for dup'ing:
+			 * "cmd1 2>&1 | cmd2": fd#1 is duped to fd#2, thus stderr
+			 * of cmd1 goes into pipe.
+			 */
+			if (setup_redirects(command, NULL)) {
+				/* Happens when redir file can't be opened:
+				 * $ hush -c 'echo FOO >&2 | echo BAR 3>/qwe/rty; echo BAZ'
+				 * FOO
+				 * hush: can't open '/qwe/rty': No such file or directory
+				 * BAZ
+				 * (echo BAR is not executed, it hits _exit(1) below)
+				 */
+				_exit(1);
+			}
+
+			/* Stores to nommu_save list of env vars putenv'ed
+			 * (NOMMU, on MMU we don't need that) */
+			/* cast away volatility... */
+			pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded);
+			/* pseudo_exec() does not return */
+		}
+
+		/* parent or error */
+#if ENABLE_HUSH_FAST
+		G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
+		enable_restore_tty_pgrp_on_exit();
+#if !BB_MMU
+		/* Clean up after vforked child */
+		free(nommu_save.argv);
+		free(nommu_save.argv_from_re_execing);
+		G.var_nest_level = sv_var_nest_level;
+		remove_nested_vars();
+		add_vars(nommu_save.old_vars);
+#endif
+		free(argv_expanded);
+		argv_expanded = NULL;
+		if (command->pid < 0) { /* [v]fork failed */
+			/* Clearly indicate, was it fork or vfork */
+			bb_simple_perror_msg(BB_MMU ? "vfork"+1 : "vfork");
+		} else {
+			pi->alive_cmds++;
+#if ENABLE_HUSH_JOB
+			/* Second and next children need to know pid of first one */
+			if (pi->pgrp < 0)
+				pi->pgrp = command->pid;
+#endif
+		}
+
+		if (cmd_no > 1)
+			close(next_infd);
+		if (cmd_no < pi->num_cmds)
+			close(pipefds.wr);
+		/* Pass read (output) pipe end to next iteration */
+		next_infd = pipefds.rd;
+	}
+
+	if (!pi->alive_cmds) {
+		debug_leave();
+		debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
+		return 1;
+	}
+
+	debug_leave();
+	debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds);
+	return -1;
+}
+
+/* NB: called by pseudo_exec, and therefore must not modify any
+ * global data until exec/_exit (we can be a child after vfork!) */
+static int run_list(struct pipe *pi)
+{
+#if ENABLE_HUSH_CASE
+	char *case_word = NULL;
+#endif
+#if ENABLE_HUSH_LOOPS
+	struct pipe *loop_top = NULL;
+	char **for_lcur = NULL;
+	char **for_list = NULL;
+#endif
+	smallint last_followup;
+	smalluint rcode;
+#if ENABLE_HUSH_IF || ENABLE_HUSH_CASE
+	smalluint cond_code = 0;
+#else
+	enum { cond_code = 0 };
+#endif
+#if HAS_KEYWORDS
+	smallint rword;      /* RES_foo */
+	smallint last_rword; /* ditto */
+#endif
+
+	debug_printf_exec("run_list start lvl %d\n", G.run_list_level);
+	debug_enter();
+
+#if ENABLE_HUSH_LOOPS
+	/* Check syntax for "for" */
+	{
+		struct pipe *cpipe;
+		for (cpipe = pi; cpipe; cpipe = cpipe->next) {
+			if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
+				continue;
+			/* current word is FOR or IN (BOLD in comments below) */
+			if (cpipe->next == NULL) {
+				syntax_error("malformed for");
+				debug_leave();
+				debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+				return 1;
+			}
+			/* "FOR v; do ..." and "for v IN a b; do..." are ok */
+			if (cpipe->next->res_word == RES_DO)
+				continue;
+			/* next word is not "do". It must be "in" then ("FOR v in ...") */
+			if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
+			 || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
+			) {
+				syntax_error("malformed for");
+				debug_leave();
+				debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+				return 1;
+			}
+		}
+	}
+#endif
+
+	/* Past this point, all code paths should jump to ret: label
+	 * in order to return, no direct "return" statements please.
+	 * This helps to ensure that no memory is leaked. */
+
+#if ENABLE_HUSH_JOB
+	G.run_list_level++;
+#endif
+
+#if HAS_KEYWORDS
+	rword = RES_NONE;
+	last_rword = RES_XXXX;
+#endif
+	last_followup = PIPE_SEQ;
+	rcode = G.last_exitcode;
+
+	/* 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;
+		if (G_flag_return_in_progress == 1)
+			break;
+
+		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 ENABLE_HUSH_IF
+		    rword == RES_IF || rword == RES_ELIF ||
+#endif
+		    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 */
+		) {
+			/* start of a loop: remember where loop starts */
+			loop_top = pi;
+			G.depth_of_loop++;
+		}
+#endif
+		/* Still in the same "if...", "then..." or "do..." branch? */
+		if (IF_HAS_KEYWORDS(rword == last_rword &&) 1) {
+			if ((rcode == 0 && last_followup == PIPE_OR)
+			 || (rcode != 0 && last_followup == PIPE_AND)
+			) {
+				/* It is "<true> || CMD" or "<false> && CMD"
+				 * and we should not execute CMD */
+				debug_printf_exec("skipped cmd because of || or &&\n");
+				last_followup = pi->followup;
+				goto dont_check_jobs_but_continue;
+			}
+		}
+		last_followup = pi->followup;
+		IF_HAS_KEYWORDS(last_rword = rword;)
+#if ENABLE_HUSH_IF
+		if (cond_code) {
+			if (rword == RES_THEN) {
+				/* if false; then ... fi has exitcode 0! */
+				G.last_exitcode = rcode = EXIT_SUCCESS;
+				/* "if <false> THEN cmd": skip cmd */
+				continue;
+			}
+		} else {
+			if (rword == RES_ELSE || rword == RES_ELIF) {
+				/* "if <true> then ... ELSE/ELIF cmd":
+				 * skip cmd and all following ones */
+				break;
+			}
+		}
+#endif
+#if ENABLE_HUSH_LOOPS
+		if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */
+			if (!for_lcur) {
+				/* first loop through for */
+
+				static const char encoded_dollar_at[] ALIGN1 = {
+					SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0'
+				}; /* encoded representation of "$@" */
+				static const char *const encoded_dollar_at_argv[] = {
+					encoded_dollar_at, NULL
+				}; /* argv list with one element: "$@" */
+				char **vals;
+
+				G.last_exitcode = rcode = EXIT_SUCCESS;
+				vals = (char**)encoded_dollar_at_argv;
+				if (pi->next->res_word == RES_IN) {
+					/* if no variable values after "in" we skip "for" */
+					if (!pi->next->cmds[0].argv) {
+						debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n");
+						break;
+					}
+					vals = pi->next->cmds[0].argv;
+				} /* else: "for var; do..." -> assume "$@" list */
+				/* create list of variable values */
+				debug_print_strings("for_list made from", vals);
+				for_list = expand_strvec_to_strvec(vals);
+				for_lcur = for_list;
+				debug_print_strings("for_list", for_list);
+			}
+			if (!*for_lcur) {
+				/* "for" loop is over, clean up */
+				free(for_list);
+				for_list = NULL;
+				for_lcur = NULL;
+				break;
+			}
+			/* 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++), /*flag:*/ 0);
+			continue;
+		}
+		if (rword == RES_IN) {
+			continue; /* "for v IN list;..." - "in" has no cmds anyway */
+		}
+		if (rword == RES_DONE) {
+			continue; /* "done" has no cmds too */
+		}
+#endif
+#if ENABLE_HUSH_CASE
+		if (rword == RES_CASE) {
+			debug_printf_exec("CASE cond_code:%d\n", cond_code);
+			case_word = expand_string_to_string(pi->cmds->argv[0],
+				EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1);
+			debug_printf_exec("CASE word1:'%s'\n", case_word);
+			//unbackslash(case_word);
+			//debug_printf_exec("CASE word2:'%s'\n", case_word);
+			continue;
+		}
+		if (rword == RES_MATCH) {
+			char **argv;
+
+			debug_printf_exec("MATCH cond_code:%d\n", cond_code);
+			if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */
+				break;
+			/* all prev words didn't match, does this one match? */
+			argv = pi->cmds->argv;
+			while (*argv) {
+				char *pattern;
+				debug_printf_exec("expand_string_to_string('%s')\n", *argv);
+				pattern = expand_string_to_string(*argv,
+						EXP_FLAG_ESC_GLOB_CHARS,
+						/*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);
+					case_word = NULL; /* make future "word)" stop */
+					break;
+				}
+				argv++;
+			}
+			continue;
+		}
+		if (rword == RES_CASE_BODY) { /* inside of a case branch */
+			debug_printf_exec("CASE_BODY cond_code:%d\n", cond_code);
+			if (cond_code != 0)
+				continue; /* not matched yet, skip this pipe */
+		}
+		if (rword == RES_ESAC) {
+			debug_printf_exec("ESAC cond_code:%d\n", cond_code);
+			if (case_word) {
+				/* "case" did not match anything: still set $? (to 0) */
+				G.last_exitcode = rcode = EXIT_SUCCESS;
+			}
+		}
+#endif
+		/* Just pressing <enter> in shell should check for jobs.
+		 * OTOH, in non-interactive shell this is useless
+		 * and only leads to extra job checks */
+		if (pi->num_cmds == 0) {
+			if (G_interactive_fd)
+				goto check_jobs_and_continue;
+			continue;
+		}
+
+		/* After analyzing all keywords and conditions, we decided
+		 * to execute this pipe. NB: have to do checkjobs(NULL)
+		 * after run_pipe to collect any background children,
+		 * even if list execution is to be stopped. */
+		debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
+#if ENABLE_HUSH_LOOPS
+		G.flag_break_continue = 0;
+#endif
+		rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
+		if (r != -1) {
+			/* We ran a builtin, function, or group.
+			 * rcode is already known
+			 * and we don't need to wait for anything. */
+			debug_printf_exec(": builtin/func exitcode %d\n", rcode);
+			G.last_exitcode = rcode;
+			check_and_run_traps();
+#if ENABLE_HUSH_TRAP && ENABLE_HUSH_FUNCTIONS
+			rcode = G.last_exitcode; /* "return" in trap can change it, read back */
+#endif
+#if ENABLE_HUSH_LOOPS
+			/* Was it "break" or "continue"? */
+			if (G.flag_break_continue) {
+				smallint fbc = G.flag_break_continue;
+				/* We might fall into outer *loop*,
+				 * don't want to break it too */
+				if (loop_top) {
+					G.depth_break_continue--;
+					if (G.depth_break_continue == 0)
+						G.flag_break_continue = 0;
+					/* else: e.g. "continue 2" should *break* once, *then* continue */
+				} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
+				if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+					checkjobs(NULL, 0 /*(no pid to wait for)*/);
+					break;
+				}
+				/* "continue": simulate end of loop */
+				rword = RES_DONE;
+				continue;
+			}
+#endif
+			if (G_flag_return_in_progress == 1) {
+				checkjobs(NULL, 0 /*(no pid to wait for)*/);
+				break;
+			}
+		} else if (pi->followup == PIPE_BG) {
+			/* What does bash do with attempts to background builtins? */
+			/* even bash 3.2 doesn't do that well with nested bg:
+			 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
+			 * I'm NOT treating inner &'s as jobs */
+#if ENABLE_HUSH_JOB
+			if (G.run_list_level == 1)
+				insert_job_into_table(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 say 0 */
+			rcode = EXIT_SUCCESS;
+			goto check_traps;
+		} else {
+#if ENABLE_HUSH_JOB
+			if (G.run_list_level == 1 && G_interactive_fd) {
+				/* Waits for completion, then fg's main shell */
+				rcode = checkjobs_and_fg_shell(pi);
+				debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
+				goto check_traps;
+			}
+#endif
+			/* This one just waits for completion */
+			rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
+			debug_printf_exec(": checkjobs exitcode %d\n", rcode);
+ check_traps:
+			G.last_exitcode = rcode;
+			check_and_run_traps();
+#if ENABLE_HUSH_TRAP && ENABLE_HUSH_FUNCTIONS
+			rcode = G.last_exitcode; /* "return" in trap can change it, read back */
+#endif
+		}
+
+		/* 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)
+			cond_code = rcode;
+#endif
+ check_jobs_and_continue:
+		checkjobs(NULL, 0 /*(no pid to wait for)*/);
+ dont_check_jobs_but_continue: ;
+#if ENABLE_HUSH_LOOPS
+		/* Beware of "while false; true; do ..."! */
+		if (pi->next
+		 && (pi->next->res_word == RES_DO || pi->next->res_word == RES_DONE)
+		 /* check for RES_DONE is needed for "while ...; do \n done" case */
+		) {
+			if (rword == RES_WHILE) {
+				if (rcode) {
+					/* "while false; do...done" - exitcode 0 */
+					G.last_exitcode = rcode = EXIT_SUCCESS;
+					debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
+					break;
+				}
+			}
+			if (rword == RES_UNTIL) {
+				if (!rcode) {
+					debug_printf_exec(": until expr is true: breaking\n");
+					break;
+				}
+			}
+		}
+#endif
+	} /* for (pi) */
+
+#if ENABLE_HUSH_JOB
+	G.run_list_level--;
+#endif
+#if ENABLE_HUSH_LOOPS
+	if (loop_top)
+		G.depth_of_loop--;
+	free(for_list);
+#endif
+#if ENABLE_HUSH_CASE
+	free(case_word);
+#endif
+	debug_leave();
+	debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
+	return rcode;
+}
+
+/* Select which version we will use */
+static int run_and_free_list(struct pipe *pi)
+{
+	int rcode = 0;
+	debug_printf_exec("run_and_free_list entered\n");
+	if (!G.o_opt[OPT_O_NOEXEC]) {
+		debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
+		rcode = run_list(pi);
+	}
+	/* free_pipe_list has the side effect of clearing memory.
+	 * In the long run that function can be merged with run_list,
+	 * but doing that now would hobble the debugging effort. */
+	free_pipe_list(pi);
+	debug_printf_exec("run_and_free_list return %d\n", rcode);
+	return rcode;
+}
+
+
+static void install_sighandlers(unsigned mask)
+{
+	sighandler_t old_handler;
+	unsigned sig = 0;
+	while ((mask >>= 1) != 0) {
+		sig++;
+		if (!(mask & 1))
+			continue;
+		old_handler = install_sighandler(sig, pick_sighandler(sig));
+		/* POSIX allows shell to re-enable SIGCHLD
+		 * even if it was SIG_IGN on entry.
+		 * Therefore we skip IGN check for it:
+		 */
+		if (sig == SIGCHLD)
+			continue;
+		/* Interactive bash re-enables SIGHUP which is SIG_IGNed on entry.
+		 * Try:
+		 * trap '' hup; bash; echo RET  # type "kill -hup $$", see SIGHUP having effect
+		 * trap '' hup; bash -c 'kill -hup $$; echo ALIVE'  # here SIGHUP is SIG_IGNed
+		 */
+		if (sig == SIGHUP && G_interactive_fd)
+			continue;
+		/* Unless one of the above signals, is it SIG_IGN? */
+		if (old_handler == SIG_IGN) {
+			/* oops... restore back to IGN, and record this fact */
+			install_sighandler(sig, old_handler);
+#if ENABLE_HUSH_TRAP
+			if (!G_traps)
+				G_traps = xzalloc(sizeof(G_traps[0]) * NSIG);
+			free(G_traps[sig]);
+			G_traps[sig] = xzalloc(1); /* == xstrdup(""); */
+#endif
+		}
+	}
+}
+
+/* Called a few times only (or even once if "sh -c") */
+static void install_special_sighandlers(void)
+{
+	unsigned mask;
+
+	/* Which signals are shell-special? */
+	mask = (1 << SIGQUIT) | (1 << SIGCHLD);
+	if (G_interactive_fd) {
+		mask |= SPECIAL_INTERACTIVE_SIGS;
+		if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
+			mask |= SPECIAL_JOBSTOP_SIGS;
+	}
+	/* Careful, do not re-install handlers we already installed */
+	if (G.special_sig_mask != mask) {
+		unsigned diff = mask & ~G.special_sig_mask;
+		G.special_sig_mask = mask;
+		install_sighandlers(diff);
+	}
+}
+
+#if ENABLE_HUSH_JOB
+/* helper */
+/* Set handlers to restore tty pgrp and exit */
+static void install_fatal_sighandlers(void)
+{
+	unsigned mask;
+
+	/* We will restore tty pgrp on these signals */
+	mask = 0
+		/*+ (1 << SIGILL ) * HUSH_DEBUG*/
+		/*+ (1 << SIGFPE ) * HUSH_DEBUG*/
+		+ (1 << SIGBUS ) * HUSH_DEBUG
+		+ (1 << SIGSEGV) * HUSH_DEBUG
+		/*+ (1 << SIGTRAP) * HUSH_DEBUG*/
+		+ (1 << SIGABRT)
+	/* bash 3.2 seems to handle these just like 'fatal' ones */
+		+ (1 << SIGPIPE)
+		+ (1 << SIGALRM)
+	/* if we are interactive, SIGHUP, SIGTERM and SIGINT are special sigs.
+	 * if we aren't interactive... but in this case
+	 * we never want to restore pgrp on exit, and this fn is not called
+	 */
+		/*+ (1 << SIGHUP )*/
+		/*+ (1 << SIGTERM)*/
+		/*+ (1 << SIGINT )*/
+	;
+	G_fatal_sig_mask = mask;
+
+	install_sighandlers(mask);
+}
+#endif
+
+static int set_mode(int state, char mode, const char *o_opt)
+{
+	int idx;
+	switch (mode) {
+	case 'n':
+		G.o_opt[OPT_O_NOEXEC] = state;
+		break;
+	case 'x':
+		IF_HUSH_MODE_X(G_x_mode = state;)
+		IF_HUSH_MODE_X(if (G.x_mode_fd <= 0) G.x_mode_fd = dup_CLOEXEC(2, 10);)
+		break;
+	case 'e':
+		G.o_opt[OPT_O_ERREXIT] = state;
+		break;
+	case 'o':
+		if (!o_opt) {
+			/* "set -o" or "set +o" without parameter.
+			 * in bash, set -o produces this output:
+			 *  pipefail        off
+			 * and set +o:
+			 *  set +o pipefail
+			 * We always use the second form.
+			 */
+			const char *p = o_opt_strings;
+			idx = 0;
+			while (*p) {
+				printf("set %co %s\n", (G.o_opt[idx] ? '-' : '+'), p);
+				idx++;
+				p += strlen(p) + 1;
+			}
+			break;
+		}
+		idx = index_in_strings(o_opt_strings, o_opt);
+		if (idx >= 0) {
+			G.o_opt[idx] = state;
+			break;
+		}
+		/* fall through to error */
+	default:
+		return EXIT_FAILURE;
+	}
+	return EXIT_SUCCESS;
+}
+
+int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hush_main(int argc, char **argv)
+{
+	pid_t cached_getpid;
+	enum {
+		OPT_login = (1 << 0),
+	};
+	unsigned flags;
+#if !BB_MMU
+	unsigned builtin_argc = 0;
+#endif
+	char **e;
+	struct variable *cur_var;
+	struct variable *shell_ver;
+
+	INIT_G();
+	if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */
+		G.last_exitcode = EXIT_SUCCESS;
+#if ENABLE_HUSH_TRAP
+# if ENABLE_HUSH_FUNCTIONS
+	G.return_exitcode = -1;
+# endif
+	G.pre_trap_exitcode = -1;
+#endif
+
+#if ENABLE_HUSH_FAST
+	G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+#endif
+#if !BB_MMU
+	G.argv0_for_re_execing = argv[0];
+#endif
+
+	cached_getpid = getpid();   /* for tcsetpgrp() during init */
+	G.root_pid = cached_getpid; /* for $PID  (NOMMU can override via -$HEXPID:HEXPPID:...) */
+	G.root_ppid = getppid();    /* for $PPID (NOMMU can override)  */
+
+	/* Deal with HUSH_VERSION */
+	debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
+	unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
+	shell_ver = xzalloc(sizeof(*shell_ver));
+	shell_ver->flg_export = 1;
+	shell_ver->flg_read_only = 1;
+	/* Code which handles ${var<op>...} needs writable values for all variables,
+	 * therefore we xstrdup: */
+	shell_ver->varstr = xstrdup(hush_version_str);
+
+	/* Create shell local variables from the values
+	 * currently living in the environment */
+	G.top_var = shell_ver;
+	cur_var = G.top_var;
+	e = environ;
+	if (e) while (*e) {
+		char *value = strchr(*e, '=');
+		if (value) { /* paranoia */
+			cur_var->next = xzalloc(sizeof(*cur_var));
+			cur_var = cur_var->next;
+			cur_var->varstr = *e;
+			cur_var->max_len = strlen(*e);
+			cur_var->flg_export = 1;
+		}
+		e++;
+	}
+	/* (Re)insert HUSH_VERSION into env (AFTER we scanned the env!) */
+	debug_printf_env("putenv '%s'\n", shell_ver->varstr);
+	putenv(shell_ver->varstr);
+
+	/* Export PWD */
+	set_pwd_var(SETFLAG_EXPORT);
+
+#if BASH_HOSTNAME_VAR
+	/* Set (but not export) HOSTNAME unless already set */
+	if (!get_local_var_value("HOSTNAME")) {
+		struct utsname uts;
+		uname(&uts);
+		set_local_var_from_halves("HOSTNAME", uts.nodename);
+	}
+#endif
+	/* IFS is not inherited from the parent environment */
+	set_local_var_from_halves("IFS", defifs);
+
+	if (!get_local_var_value("PATH"))
+		set_local_var_from_halves("PATH", bb_default_root_path);
+
+	/* PS1/PS2 are set later, if we determine that we are interactive */
+
+	/* bash also exports SHLVL and _,
+	 * and sets (but doesn't export) the following variables:
+	 * BASH=/bin/bash
+	 * BASH_VERSINFO=([0]="3" [1]="2" [2]="0" [3]="1" [4]="release" [5]="i386-pc-linux-gnu")
+	 * BASH_VERSION='3.2.0(1)-release'
+	 * HOSTTYPE=i386
+	 * MACHTYPE=i386-pc-linux-gnu
+	 * OSTYPE=linux-gnu
+	 * PPID=<NNNNN> - we also do it elsewhere
+	 * EUID=<NNNNN>
+	 * UID=<NNNNN>
+	 * GROUPS=()
+	 * LINES=<NNN>
+	 * COLUMNS=<NNN>
+	 * BASH_ARGC=()
+	 * BASH_ARGV=()
+	 * BASH_LINENO=()
+	 * BASH_SOURCE=()
+	 * DIRSTACK=()
+	 * PIPESTATUS=([0]="0")
+	 * HISTFILE=/<xxx>/.bash_history
+	 * HISTFILESIZE=500
+	 * HISTSIZE=500
+	 * MAILCHECK=60
+	 * PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.
+	 * SHELL=/bin/bash
+	 * SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
+	 * TERM=dumb
+	 * OPTERR=1
+	 * OPTIND=1
+	 * PS4='+ '
+	 */
+
+#if NUM_SCRIPTS > 0
+	if (argc < 0) {
+		char *script = get_script_content(-argc - 1);
+		G.global_argv = argv;
+		G.global_argc = string_array_len(argv);
+		//install_special_sighandlers(); - needed?
+		parse_and_run_string(script);
+		goto final_return;
+	}
+#endif
+
+	/* Initialize some more globals to non-zero values */
+	die_func = restore_ttypgrp_and__exit;
+
+	/* Shell is non-interactive at first. We need to call
+	 * install_special_sighandlers() if we are going to execute "sh <script>",
+	 * "sh -c <cmds>" or login shell's /etc/profile and friends.
+	 * If we later decide that we are interactive, we run install_special_sighandlers()
+	 * in order to intercept (more) signals.
+	 */
+
+	/* Parse options */
+	/* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+	flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
+	while (1) {
+		int opt = getopt(argc, argv, "+" /* stop at 1st non-option */
+				"cexinsl"
+#if !BB_MMU
+				"<:$:R:V:"
+# if ENABLE_HUSH_FUNCTIONS
+				"F:"
+# endif
+#endif
+		);
+		if (opt <= 0)
+			break;
+		switch (opt) {
+		case 'c':
+			/* Note: -c is not an option with param!
+			 * "hush -c -l SCRIPT" is valid. "hush -cSCRIPT" is not.
+			 */
+			G.opt_c = 1;
+			break;
+		case 'i':
+			/* Well, we cannot just declare interactiveness,
+			 * we have to have some stuff (ctty, etc) */
+			/* G_interactive_fd++; */
+			break;
+		case 's':
+			G.opt_s = 1;
+			break;
+		case 'l':
+			flags |= OPT_login;
+			break;
+#if !BB_MMU
+		case '<': /* "big heredoc" support */
+			full_write1_str(optarg);
+			_exit(0);
+		case '$': {
+			unsigned long long empty_trap_mask;
+
+			G.root_pid = bb_strtou(optarg, &optarg, 16);
+			optarg++;
+			G.root_ppid = bb_strtou(optarg, &optarg, 16);
+			optarg++;
+			G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
+			optarg++;
+			G.last_exitcode = bb_strtou(optarg, &optarg, 16);
+			optarg++;
+			builtin_argc = bb_strtou(optarg, &optarg, 16);
+			optarg++;
+			empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
+			if (empty_trap_mask != 0) {
+				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)) {
+						G_traps[sig] = xzalloc(1); /* == xstrdup(""); */
+						install_sighandler(sig, SIG_IGN);
+					}
+				}
+# endif
+			}
+# if ENABLE_HUSH_LOOPS
+			optarg++;
+			G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
+# endif
+			/* Suppress "killed by signal" message, -$ hack is used
+			 * for subshells: echo `sh -c 'kill -9 $$'`
+			 * should be silent.
+			 */
+			IF_HUSH_JOB(G.run_list_level = 1;)
+# if ENABLE_HUSH_FUNCTIONS
+			/* nommu uses re-exec trick for "... | func | ...",
+			 * should allow "return".
+			 * This accidentally allows returns in subshells.
+			 */
+			G_flag_return_in_progress = -1;
+# endif
+			break;
+		}
+		case 'R':
+		case 'V':
+			set_local_var(xstrdup(optarg), opt == 'R' ? SETFLAG_MAKE_RO : 0);
+			break;
+# if ENABLE_HUSH_FUNCTIONS
+		case 'F': {
+			struct function *funcp = new_function(optarg);
+			/* funcp->name is already set to optarg */
+			/* funcp->body is set to NULL. It's a special case. */
+			funcp->body_as_string = argv[optind];
+			optind++;
+			break;
+		}
+# endif
+#endif
+		/*case '?': invalid option encountered (set_mode('?') will fail) */
+		/*case 'n':*/
+		/*case 'x':*/
+		/*case 'e':*/
+		default:
+			if (set_mode(1, opt, NULL) == 0) /* no error */
+				break;
+			bb_show_usage();
+		}
+	} /* option parsing loop */
+
+	/* Skip options. Try "hush -l": $1 should not be "-l"! */
+	G.global_argc = argc - (optind - 1);
+	G.global_argv = argv + (optind - 1);
+	G.global_argv[0] = argv[0];
+
+	/* If we are login shell... */
+	if (flags & OPT_login) {
+		const char *hp = NULL;
+		HFILE *input;
+
+		debug_printf("sourcing /etc/profile\n");
+		input = hfopen("/etc/profile");
+ run_profile:
+		if (input != NULL) {
+			install_special_sighandlers();
+			parse_and_run_file(input);
+			hfclose(input);
+		}
+		/* bash: after sourcing /etc/profile,
+		 * tries to source (in the given order):
+		 * ~/.bash_profile, ~/.bash_login, ~/.profile,
+		 * stopping on first found. --noprofile turns this off.
+		 * bash also sources ~/.bash_logout on exit.
+		 * If called as sh, skips .bash_XXX files.
+		 */
+		if (!hp) { /* unless we looped on the "goto" already */
+			hp = get_local_var_value("HOME");
+			if (hp && hp[0]) {
+				debug_printf("sourcing ~/.profile\n");
+				hp = concat_path_file(hp, ".profile");
+				input = hfopen(hp);
+				free((char*)hp);
+				goto run_profile;
+			}
+		}
+	}
+
+	/* -c takes effect *after* -l */
+	if (G.opt_c) {
+		/* Possibilities:
+		 * sh ... -c 'script'
+		 * sh ... -c 'script' ARG0 [ARG1...]
+		 * On NOMMU, if builtin_argc != 0,
+		 * sh ... -c 'builtin' BARGV... "" ARG0 [ARG1...]
+		 * "" needs to be replaced with NULL
+		 * and BARGV vector fed to builtin function.
+		 * Note: the form without ARG0 never happens:
+		 * sh ... -c 'builtin' BARGV... ""
+		 */
+		char *script;
+
+		install_special_sighandlers();
+
+		G.global_argc--;
+		G.global_argv++;
+#if !BB_MMU
+		if (builtin_argc) {
+			/* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
+			const struct built_in_command *x;
+			x = find_builtin(G.global_argv[0]);
+			if (x) { /* paranoia */
+				argv = G.global_argv;
+				G.global_argc -= builtin_argc + 1; /* skip [BARGV...] "" */
+				G.global_argv += builtin_argc + 1;
+				G.global_argv[-1] = NULL; /* replace "" */
+				G.last_exitcode = x->b_function(argv);
+			}
+			goto final_return;
+		}
+#endif
+
+		script = G.global_argv[0];
+		if (!script)
+			bb_error_msg_and_die(bb_msg_requires_arg, "-c");
+		if (!G.global_argv[1]) {
+			/* -c 'script' (no params): prevent empty $0 */
+			G.global_argv[0] = argv[0];
+		} else { /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
+			G.global_argc--;
+			G.global_argv++;
+		}
+		parse_and_run_string(script);
+		goto final_return;
+	}
+
+	/* -s is: hush -s ARGV1 ARGV2 (no SCRIPT) */
+	if (!G.opt_s && G.global_argv[1]) {
+		HFILE *input;
+		/*
+		 * "bash <script>" (which is never interactive (unless -i?))
+		 * sources $BASH_ENV here (without scanning $PATH).
+		 * If called as sh, does the same but with $ENV.
+		 * Also NB, per POSIX, $ENV should undergo parameter expansion.
+		 */
+		G.global_argc--;
+		G.global_argv++;
+		debug_printf("running script '%s'\n", G.global_argv[0]);
+		xfunc_error_retval = 127; /* for "hush /does/not/exist" case */
+		input = hfopen(G.global_argv[0]);
+		if (!input) {
+			bb_simple_perror_msg_and_die(G.global_argv[0]);
+		}
+		xfunc_error_retval = 1;
+		install_special_sighandlers();
+		parse_and_run_file(input);
+#if ENABLE_FEATURE_CLEAN_UP
+		hfclose(input);
+#endif
+		goto final_return;
+	}
+	/* "implicit" -s: bare interactive hush shows 's' in $- */
+	G.opt_s = 1;
+
+	/* Up to here, shell was non-interactive. Now it may become one.
+	 * NB: don't forget to (re)run install_special_sighandlers() as needed.
+	 */
+
+	/* A shell is interactive if the '-i' flag was given,
+	 * or if all of the following conditions are met:
+	 *    no -c command
+	 *    no arguments remaining or the -s flag given
+	 *    standard input is a terminal
+	 *    standard output is a terminal
+	 * Refer to Posix.2, the description of the 'sh' utility.
+	 */
+#if ENABLE_HUSH_JOB
+	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+		G_saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
+		debug_printf("saved_tty_pgrp:%d\n", G_saved_tty_pgrp);
+		if (G_saved_tty_pgrp < 0)
+			G_saved_tty_pgrp = 0;
+
+		/* try to dup stdin to high fd#, >= 255 */
+		G_interactive_fd = dup_CLOEXEC(STDIN_FILENO, 254);
+		if (G_interactive_fd < 0) {
+			/* try to dup to any fd */
+			G_interactive_fd = dup(STDIN_FILENO);
+			if (G_interactive_fd < 0) {
+				/* give up */
+				G_interactive_fd = 0;
+				G_saved_tty_pgrp = 0;
+			}
+		}
+	}
+	debug_printf("interactive_fd:%d\n", G_interactive_fd);
+	if (G_interactive_fd) {
+		close_on_exec_on(G_interactive_fd);
+
+		if (G_saved_tty_pgrp) {
+			/* If we were run as 'hush &', sleep until we are
+			 * in the foreground (tty pgrp == our pgrp).
+			 * If we get started under a job aware app (like bash),
+			 * make sure we are now in charge so we don't fight over
+			 * who gets the foreground */
+			while (1) {
+				pid_t shell_pgrp = getpgrp();
+				G_saved_tty_pgrp = tcgetpgrp(G_interactive_fd);
+				if (G_saved_tty_pgrp == shell_pgrp)
+					break;
+				/* send TTIN to ourself (should stop us) */
+				kill(- shell_pgrp, SIGTTIN);
+			}
+		}
+
+		/* Install more signal handlers */
+		install_special_sighandlers();
+
+		if (G_saved_tty_pgrp) {
+			/* Set other signals to restore saved_tty_pgrp */
+			install_fatal_sighandlers();
+			/* Put ourselves in our own process group
+			 * (bash, too, does this only if ctty is available) */
+			bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
+			/* Grab control of the terminal */
+			tcsetpgrp(G_interactive_fd, cached_getpid);
+		}
+		enable_restore_tty_pgrp_on_exit();
+
+# if ENABLE_FEATURE_EDITING
+		G.line_input_state = new_line_input_t(FOR_SHELL);
+#  if EDITING_HAS_get_exe_name
+		G.line_input_state->get_exe_name = get_builtin_name;
+#  endif
+# endif
+# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
+		{
+			const char *hp = get_local_var_value("HISTFILE");
+			if (!hp) {
+				hp = get_local_var_value("HOME");
+				if (hp)
+					hp = concat_path_file(hp, ".hush_history");
+			} else {
+				hp = xstrdup(hp);
+			}
+			if (hp) {
+				G.line_input_state->hist_file = hp;
+				//set_local_var(xasprintf("HISTFILE=%s", ...));
+			}
+#  if ENABLE_FEATURE_SH_HISTFILESIZE
+			hp = get_local_var_value("HISTFILESIZE");
+			G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+#  endif
+		}
+# endif
+	} else {
+		install_special_sighandlers();
+	}
+#elif ENABLE_HUSH_INTERACTIVE
+	/* No job control compiled in, only prompt/line editing */
+	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+		G_interactive_fd = dup_CLOEXEC(STDIN_FILENO, 254);
+		if (G_interactive_fd < 0) {
+			/* try to dup to any fd */
+			G_interactive_fd = dup_CLOEXEC(STDIN_FILENO, -1);
+			if (G_interactive_fd < 0)
+				/* give up */
+				G_interactive_fd = 0;
+		}
+	}
+	if (G_interactive_fd) {
+		close_on_exec_on(G_interactive_fd);
+	}
+	install_special_sighandlers();
+#else
+	/* We have interactiveness code disabled */
+	install_special_sighandlers();
+#endif
+	/* bash:
+	 * if interactive but not a login shell, sources ~/.bashrc
+	 * (--norc turns this off, --rcfile <file> overrides)
+	 */
+
+	if (G_interactive_fd) {
+#if ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT
+		/* Set (but not export) PS1/2 unless already set */
+		if (!get_local_var_value("PS1"))
+			set_local_var_from_halves("PS1", "\\w \\$ ");
+		if (!get_local_var_value("PS2"))
+			set_local_var_from_halves("PS2", "> ");
+#endif
+		if (!ENABLE_FEATURE_SH_EXTRA_QUIET) {
+			/* note: ash and hush share this string */
+			printf("\n\n%s %s\n"
+				IF_HUSH_HELP("Enter 'help' for a list of built-in commands.\n")
+				"\n",
+				bb_banner,
+				"hush - the humble shell"
+			);
+		}
+	}
+
+	parse_and_run_file(hfopen(NULL)); /* stdin */
+
+ final_return:
+	hush_exit(G.last_exitcode);
+}
+
+
+/*
+ * Built-ins
+ */
+static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM)
+{
+	return 0;
+}
+
+#if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL
+static NOINLINE int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
+{
+	int argc = string_array_len(argv);
+	return applet_main_func(argc, argv);
+}
+#endif
+#if ENABLE_HUSH_TEST || BASH_TEST2
+static int FAST_FUNC builtin_test(char **argv)
+{
+	return run_applet_main(argv, test_main);
+}
+#endif
+#if ENABLE_HUSH_ECHO
+static int FAST_FUNC builtin_echo(char **argv)
+{
+	return run_applet_main(argv, echo_main);
+}
+#endif
+#if ENABLE_HUSH_PRINTF
+static int FAST_FUNC builtin_printf(char **argv)
+{
+	return run_applet_main(argv, printf_main);
+}
+#endif
+
+#if ENABLE_HUSH_HELP
+static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM)
+{
+	const struct built_in_command *x;
+
+	printf(
+		"Built-in commands:\n"
+		"------------------\n");
+	for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) {
+		if (x->b_descr)
+			printf("%-10s%s\n", x->b_cmd, x->b_descr);
+	}
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+	show_history(G.line_input_state);
+	return EXIT_SUCCESS;
+}
+#endif
+
+static char **skip_dash_dash(char **argv)
+{
+	argv++;
+	if (argv[0] && argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == '\0')
+		argv++;
+	return argv;
+}
+
+static int FAST_FUNC builtin_cd(char **argv)
+{
+	const char *newdir;
+
+	argv = skip_dash_dash(argv);
+	newdir = argv[0];
+	if (newdir == NULL) {
+		/* bash does nothing (exitcode 0) if HOME is ""; if it's unset,
+		 * bash says "bash: cd: HOME not set" and does nothing
+		 * (exitcode 1)
+		 */
+		const char *home = get_local_var_value("HOME");
+		newdir = home ? home : "/";
+	}
+	if (chdir(newdir)) {
+		/* Mimic bash message exactly */
+		bb_perror_msg("cd: %s", newdir);
+		return EXIT_FAILURE;
+	}
+	/* Read current dir (get_cwd(1) is inside) and set PWD.
+	 * 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(/*flag:*/ 0);
+	return EXIT_SUCCESS;
+}
+
+static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
+{
+	puts(get_cwd(0));
+	return EXIT_SUCCESS;
+}
+
+static int FAST_FUNC builtin_eval(char **argv)
+{
+	argv = skip_dash_dash(argv);
+
+	if (!argv[0])
+		return EXIT_SUCCESS;
+
+	IF_HUSH_MODE_X(G.x_mode_depth++;)
+	//bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth);
+	if (!argv[1]) {
+		/* bash:
+		 * eval "echo Hi; done" ("done" is syntax error):
+		 * "echo Hi" will not execute too.
+		 */
+		parse_and_run_string(argv[0]);
+	} else {
+		/* "The eval utility shall construct a command by
+		 * concatenating arguments together, separating
+		 * each with a <space> character."
+		 */
+		char *str, *p;
+		unsigned len = 0;
+		char **pp = argv;
+		do
+			len += strlen(*pp) + 1;
+		while (*++pp);
+		str = p = xmalloc(len);
+		pp = argv;
+		for (;;) {
+			p = stpcpy(p, *pp);
+			pp++;
+			if (!*pp)
+				break;
+			*p++ = ' ';
+		}
+		parse_and_run_string(str);
+		free(str);
+	}
+	IF_HUSH_MODE_X(G.x_mode_depth--;)
+	//bb_error_msg("%s: --x_mode_depth=%d", __func__, G.x_mode_depth);
+	return G.last_exitcode;
+}
+
+static int FAST_FUNC builtin_exec(char **argv)
+{
+	argv = skip_dash_dash(argv);
+	if (argv[0] == NULL)
+		return EXIT_SUCCESS; /* bash does this */
+
+	/* Careful: we can end up here after [v]fork. Do not restore
+	 * tty pgrp then, only top-level shell process does that */
+	if (G_saved_tty_pgrp && getpid() == G.root_pid)
+		tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+
+	/* Saved-redirect fds, script fds and G_interactive_fd are still
+	 * open here. However, they are all CLOEXEC, and execv below
+	 * closes them. Try interactive "exec ls -l /proc/self/fd",
+	 * it should show no extra open fds in the "ls" process.
+	 * If we'd try to run builtins/NOEXECs, this would need improving.
+	 */
+	//close_saved_fds_and_FILE_fds();
+
+	/* TODO: if exec fails, bash does NOT exit! We do.
+	 * We'll need to undo trap cleanup (it's inside execvp_or_die)
+	 * and tcsetpgrp, and this is inherently racy.
+	 */
+	execvp_or_die(argv);
+}
+
+static int FAST_FUNC builtin_exit(char **argv)
+{
+	debug_printf_exec("%s()\n", __func__);
+
+	/* interactive bash:
+	 * # trap "echo EEE" EXIT
+	 * # exit
+	 * exit
+	 * There are stopped jobs.
+	 * (if there are _stopped_ jobs, running ones don't count)
+	 * # exit
+	 * exit
+	 * EEE (then bash exits)
+	 *
+	 * TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
+	 */
+
+	/* note: EXIT trap is run by hush_exit */
+	argv = skip_dash_dash(argv);
+	if (argv[0] == NULL) {
+#if ENABLE_HUSH_TRAP
+		if (G.pre_trap_exitcode >= 0) /* "exit" in trap uses $? from before the trap */
+			hush_exit(G.pre_trap_exitcode);
+#endif
+		hush_exit(G.last_exitcode);
+	}
+	/* mimic bash: exit 123abc == exit 255 + error msg */
+	xfunc_error_retval = 255;
+	/* bash: exit -2 == exit 254, no error msg */
+	hush_exit(xatoi(argv[0]) & 0xff);
+}
+
+#if ENABLE_HUSH_TYPE
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/type.html */
+static int FAST_FUNC builtin_type(char **argv)
+{
+	int ret = EXIT_SUCCESS;
+
+	while (*++argv) {
+		const char *type;
+		char *path = NULL;
+
+		if (0) {} /* make conditional compile easier below */
+		/*else if (find_alias(*argv))
+			type = "an alias";*/
+# if ENABLE_HUSH_FUNCTIONS
+		else if (find_function(*argv))
+			type = "a function";
+# endif
+		else if (find_builtin(*argv))
+			type = "a shell builtin";
+		else if ((path = find_in_path(*argv)) != NULL)
+			type = path;
+		else {
+			bb_error_msg("type: %s: not found", *argv);
+			ret = EXIT_FAILURE;
+			continue;
+		}
+
+		printf("%s is %s\n", *argv, type);
+		free(path);
+	}
+
+	return ret;
+}
+#endif
+
+#if ENABLE_HUSH_READ
+/* Interruptibility of read builtin in bash
+ * (tested on bash-4.2.8 by sending signals (not by ^C)):
+ *
+ * Empty trap makes read ignore corresponding signal, for any signal.
+ *
+ * SIGINT:
+ * - terminates non-interactive shell;
+ * - interrupts read in interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to command prompt in interactive shell;
+ * - executes trap and returns to read in non-interactive shell;
+ * SIGTERM:
+ * - is ignored (does not interrupt) read in interactive shell;
+ * - terminates non-interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ * SIGHUP:
+ * - terminates shell (regardless of interactivity);
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ * SIGCHLD from children:
+ * - does not interrupt read regardless of interactivity:
+ *   try: sleep 1 & read x; echo $x
+ */
+static int FAST_FUNC builtin_read(char **argv)
+{
+	const char *r;
+	struct builtin_read_params params;
+
+	memset(&params, 0, sizeof(params));
+
+	/* "!": do not abort on errors.
+	 * Option string must start with "sr" to match BUILTIN_READ_xxx
+	 */
+	params.read_flags = getopt32(argv,
+# if BASH_READ_D
+		IF_NOT_HUSH_BASH_COMPAT("^")
+		"!srn:p:t:u:d:" IF_NOT_HUSH_BASH_COMPAT("\0" "-1"/*min 1 arg*/),
+		&params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u, &params.opt_d
+# else
+		IF_NOT_HUSH_BASH_COMPAT("^")
+		"!srn:p:t:u:" IF_NOT_HUSH_BASH_COMPAT("\0" "-1"/*min 1 arg*/),
+		&params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u
+# endif
+//TODO: print "read: need variable name"
+//for the case of !BASH "read" with no args (now it fails silently)
+//(or maybe extend getopt32() to emit a message if "-1" fails)
+	);
+	if ((uint32_t)params.read_flags == (uint32_t)-1)
+		return EXIT_FAILURE;
+	argv += optind;
+	params.argv = argv;
+	params.setvar = set_local_var_from_halves;
+	params.ifs = get_local_var_value("IFS"); /* can be NULL */
+
+ again:
+	r = shell_builtin_read(&params);
+
+	if ((uintptr_t)r == 1 && errno == EINTR) {
+		unsigned sig = check_and_run_traps();
+		if (sig != SIGINT)
+			goto again;
+	}
+
+	if ((uintptr_t)r > 1) {
+		bb_simple_error_msg(r);
+		r = (char*)(uintptr_t)1;
+	}
+
+	return (uintptr_t)r;
+}
+#endif
+
+#if ENABLE_HUSH_UMASK
+static int FAST_FUNC builtin_umask(char **argv)
+{
+	int rc;
+	mode_t mask;
+
+	rc = 1;
+	mask = umask(0);
+	argv = skip_dash_dash(argv);
+	if (argv[0]) {
+		mode_t old_mask = mask;
+
+		/* numeric umasks are taken as-is */
+		/* symbolic umasks are inverted: "umask a=rx" calls umask(222) */
+		if (!isdigit(argv[0][0]))
+			mask ^= 0777;
+		mask = bb_parse_mode(argv[0], mask);
+		if (!isdigit(argv[0][0]))
+			mask ^= 0777;
+		if ((unsigned)mask > 0777) {
+			mask = old_mask;
+			/* bash messages:
+			 * bash: umask: 'q': invalid symbolic mode operator
+			 * bash: umask: 999: octal number out of range
+			 */
+			bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]);
+			rc = 0;
+		}
+	} else {
+		/* Mimic bash */
+		printf("%04o\n", (unsigned) mask);
+		/* fall through and restore mask which we set to 0 */
+	}
+	umask(mask);
+
+	return !rc; /* rc != 0 - success */
+}
+#endif
+
+#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_TRAP
+static void print_escaped(const char *s)
+{
+	if (*s == '\'')
+		goto squote;
+	do {
+		const char *p = strchrnul(s, '\'');
+		/* print 'xxxx', possibly just '' */
+		printf("'%.*s'", (int)(p - s), s);
+		if (*p == '\0')
+			break;
+		s = p;
+ squote:
+		/* s points to '; print "'''...'''" */
+		putchar('"');
+		do putchar('\''); while (*++s == '\'');
+		putchar('"');
+	} while (*s);
+}
+#endif
+
+#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY
+static int helper_export_local(char **argv, unsigned flags)
+{
+	do {
+		char *name = *argv;
+		const char *name_end = endofname(name);
+
+		if (*name_end == '\0') {
+			struct variable *var, **vpp;
+
+			vpp = get_ptr_to_local_var(name, name_end - name);
+			var = vpp ? *vpp : NULL;
+
+			if (flags & SETFLAG_UNEXPORT) {
+				/* export -n NAME (without =VALUE) */
+				if (var) {
+					var->flg_export = 0;
+					debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
+					unsetenv(name);
+				} /* else: export -n NOT_EXISTING_VAR: no-op */
+				continue;
+			}
+			if (flags & SETFLAG_EXPORT) {
+				/* export NAME (without =VALUE) */
+				if (var) {
+					var->flg_export = 1;
+					debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
+					putenv(var->varstr);
+					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))) {
+				unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT;
+				if (var && var->var_nest_level == lvl) {
+					/* "local x=abc; ...; local x" - ignore second local decl */
+					continue;
+				}
+			}
+# endif
+			/* Exporting non-existing variable.
+			 * 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.
+			 */
+			/* Or, it's "local NAME" (without =VALUE).
+			 * 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 {
+			if (*name_end != '=') {
+				bb_error_msg("'%s': bad variable name", name);
+				/* do not parse following argv[]s: */
+				return 1;
+			}
+			/* (Un)exporting/making local NAME=VALUE */
+			name = xstrdup(name);
+			/* Testcase: export PS1='\w \$ ' */
+			unbackslash(name);
+		}
+		debug_printf_env("%s: set_local_var('%s')\n", __func__, name);
+		if (set_local_var(name, flags))
+			return EXIT_FAILURE;
+	} while (*++argv);
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_EXPORT
+static int FAST_FUNC builtin_export(char **argv)
+{
+	unsigned opt_unexport;
+
+# if ENABLE_HUSH_EXPORT_N
+	/* "!": do not abort on errors */
+	opt_unexport = getopt32(argv, "!n");
+	if (opt_unexport == (uint32_t)-1)
+		return EXIT_FAILURE;
+	argv += optind;
+# else
+	opt_unexport = 0;
+	argv++;
+# endif
+
+	if (argv[0] == NULL) {
+		char **e = environ;
+		if (e) {
+			while (*e) {
+# if 0
+				puts(*e++);
+# else
+				/* ash emits: export VAR='VAL'
+				 * bash: declare -x VAR="VAL"
+				 * we follow ash example */
+				const char *s = *e++;
+				const char *p = strchr(s, '=');
+
+				if (!p) /* wtf? take next variable */
+					continue;
+				/* export var= */
+				printf("export %.*s", (int)(p - s) + 1, s);
+				print_escaped(p + 1);
+				putchar('\n');
+# endif
+			}
+			/*fflush_all(); - done after each builtin anyway */
+		}
+		return EXIT_SUCCESS;
+	}
+
+	return helper_export_local(argv, opt_unexport ? SETFLAG_UNEXPORT : SETFLAG_EXPORT);
+}
+#endif
+
+#if ENABLE_HUSH_LOCAL
+static int FAST_FUNC builtin_local(char **argv)
+{
+	if (G.func_nest_level == 0) {
+		bb_error_msg("%s: not in a function", argv[0]);
+		return EXIT_FAILURE; /* bash compat */
+	}
+	argv++;
+	/* Since all builtins run in a nested variable level,
+	 * need to use level - 1 here. Or else the variable will be removed at once
+	 * after builtin returns.
+	 */
+	return helper_export_local(argv, (G.var_nest_level - 1) << SETFLAG_VARLVL_SHIFT);
+}
+#endif
+
+#if ENABLE_HUSH_READONLY
+static int FAST_FUNC builtin_readonly(char **argv)
+{
+	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, SETFLAG_MAKE_RO);
+}
+#endif
+
+#if ENABLE_HUSH_UNSET
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
+static int FAST_FUNC builtin_unset(char **argv)
+{
+	int ret;
+	unsigned opts;
+
+	/* "!": do not abort on errors */
+	/* "+": stop at 1st non-option */
+	opts = getopt32(argv, "!+vf");
+	if (opts == (unsigned)-1)
+		return EXIT_FAILURE;
+	if (opts == 3) {
+		bb_simple_error_msg("unset: -v and -f are exclusive");
+		return EXIT_FAILURE;
+	}
+	argv += optind;
+
+	ret = EXIT_SUCCESS;
+	while (*argv) {
+		if (!(opts & 2)) { /* not -f */
+			if (unset_local_var(*argv)) {
+				/* unset <nonexistent_var> doesn't fail.
+				 * Error is when one tries to unset RO var.
+				 * Message was printed by unset_local_var. */
+				ret = EXIT_FAILURE;
+			}
+		}
+# if ENABLE_HUSH_FUNCTIONS
+		else {
+			unset_func(*argv);
+		}
+# endif
+		argv++;
+	}
+	return ret;
+}
+#endif
+
+#if ENABLE_HUSH_SET
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
+ * built-in 'set' handler
+ * SUSv3 says:
+ * set [-abCefhmnuvx] [-o option] [argument...]
+ * set [+abCefhmnuvx] [+o option] [argument...]
+ * set -- [argument...]
+ * set -o
+ * set +o
+ * Implementations shall support the options in both their hyphen and
+ * plus-sign forms. These options can also be specified as options to sh.
+ * Examples:
+ * Write out all variables and their values: set
+ * Set $1, $2, and $3 and set "$#" to 3: set c a b
+ * Turn on the -x and -v options: set -xv
+ * Unset all positional parameters: set --
+ * Set $1 to the value of x, even if it begins with '-' or '+': set -- "$x"
+ * Set the positional parameters to the expansion of x, even if x expands
+ * with a leading '-' or '+': set -- $x
+ *
+ * So far, we only support "set -- [argument...]" and some of the short names.
+ */
+static int FAST_FUNC builtin_set(char **argv)
+{
+	int n;
+	char **pp, **g_argv;
+	char *arg = *++argv;
+
+	if (arg == NULL) {
+		struct variable *e;
+		for (e = G.top_var; e; e = e->next)
+			puts(e->varstr);
+		return EXIT_SUCCESS;
+	}
+
+	do {
+		if (strcmp(arg, "--") == 0) {
+			++argv;
+			goto set_argv;
+		}
+		if (arg[0] != '+' && arg[0] != '-')
+			break;
+		for (n = 1; arg[n]; ++n) {
+			if (set_mode((arg[0] == '-'), arg[n], argv[1])) {
+				bb_error_msg("%s: %s: invalid option", "set", arg);
+				return EXIT_FAILURE;
+			}
+			if (arg[n] == 'o' && argv[1])
+				argv++;
+		}
+	} while ((arg = *++argv) != NULL);
+	/* Now argv[0] is 1st argument */
+
+	if (arg == NULL)
+		return EXIT_SUCCESS;
+ set_argv:
+
+	/* NB: G.global_argv[0] ($0) is never freed/changed */
+	g_argv = G.global_argv;
+	if (G.global_args_malloced) {
+		pp = g_argv;
+		while (*++pp)
+			free(*pp);
+		g_argv[1] = NULL;
+	} else {
+		G.global_args_malloced = 1;
+		pp = xzalloc(sizeof(pp[0]) * 2);
+		pp[0] = g_argv[0]; /* retain $0 */
+		g_argv = pp;
+	}
+	/* This realloc's G.global_argv */
+	G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1);
+
+	G.global_argc = 1 + string_array_len(pp + 1);
+
+	return EXIT_SUCCESS;
+}
+#endif
+
+static int FAST_FUNC builtin_shift(char **argv)
+{
+	int n = 1;
+	argv = skip_dash_dash(argv);
+	if (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) {
+			int m = 1;
+			while (m <= n)
+				free(G.global_argv[m++]);
+		}
+		G.global_argc -= n;
+		memmove(&G.global_argv[1], &G.global_argv[n+1],
+				G.global_argc * sizeof(G.global_argv[0]));
+		return EXIT_SUCCESS;
+	}
+	return EXIT_FAILURE;
+}
+
+#if ENABLE_HUSH_GETOPTS
+static int FAST_FUNC builtin_getopts(char **argv)
+{
+/* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html
+
+TODO:
+If a required argument is not found, and getopts is not silent,
+a question mark (?) is placed in VAR, OPTARG is unset, and a
+diagnostic message is printed.  If getopts is silent, then a
+colon (:) is placed in VAR and OPTARG is set to the option
+character found.
+
+Test that VAR is a valid variable name?
+
+"Whenever the shell is invoked, OPTIND shall be initialized to 1"
+*/
+	char cbuf[2];
+	const char *cp, *optstring, *var;
+	int c, n, exitcode, my_opterr;
+	unsigned count;
+
+	optstring = *++argv;
+	if (!optstring || !(var = *++argv)) {
+		bb_simple_error_msg("usage: getopts OPTSTRING VAR [ARGS]");
+		return EXIT_FAILURE;
+	}
+
+	if (argv[1])
+		argv[0] = G.global_argv[0]; /* for error messages in getopt() */
+	else
+		argv = G.global_argv;
+	cbuf[1] = '\0';
+
+	my_opterr = 0;
+	if (optstring[0] != ':') {
+		cp = get_local_var_value("OPTERR");
+		/* 0 if "OPTERR=0", 1 otherwise */
+		my_opterr = (!cp || NOT_LONE_CHAR(cp, '0'));
+	}
+
+	/* getopts stops on first non-option. Add "+" to force that */
+	/*if (optstring[0] != '+')*/ {
+		char *s = alloca(strlen(optstring) + 2);
+		sprintf(s, "+%s", optstring);
+		optstring = s;
+	}
+
+	/* Naively, now we should just
+	 *	cp = get_local_var_value("OPTIND");
+	 *	optind = cp ? atoi(cp) : 0;
+	 *	optarg = NULL;
+	 *	opterr = my_opterr;
+	 *	c = getopt(string_array_len(argv), argv, optstring);
+	 * and be done? Not so fast...
+	 * Unlike normal getopt() usage in C programs, here
+	 * each successive call will (usually) have the same argv[] CONTENTS,
+	 * but not the ADDRESSES. Worse yet, it's possible that between
+	 * invocations of "getopts", there will be calls to shell builtins
+	 * which use getopt() internally. Example:
+	 *	while getopts "abc" RES -a -bc -abc de; do
+	 *		unset -ff func
+	 *	done
+	 * This would not work correctly: getopt() call inside "unset"
+	 * modifies internal libc state which is tracking position in
+	 * multi-option strings ("-abc"). At best, it can skip options
+	 * or return the same option infinitely. With glibc implementation
+	 * of getopt(), it would use outright invalid pointers and return
+	 * garbage even _without_ "unset" mangling internal state.
+	 *
+	 * We resort to resetting getopt() state and calling it N times,
+	 * until we get Nth result (or failure).
+	 * (N == G.getopt_count is reset to 0 whenever OPTIND is [un]set).
+	 */
+	GETOPT_RESET();
+	count = 0;
+	n = string_array_len(argv);
+	do {
+		optarg = NULL;
+		opterr = (count < G.getopt_count) ? 0 : my_opterr;
+		c = getopt(n, argv, optstring);
+		if (c < 0)
+			break;
+		count++;
+	} while (count <= G.getopt_count);
+
+	/* Set OPTIND. Prevent resetting of the magic counter! */
+	set_local_var_from_halves("OPTIND", utoa(optind));
+	G.getopt_count = count; /* "next time, give me N+1'th result" */
+	GETOPT_RESET(); /* just in case */
+
+	/* Set OPTARG */
+	/* Always set or unset, never left as-is, even on exit/error:
+	 * "If no option was found, or if the option that was found
+	 * does not have an option-argument, OPTARG shall be unset."
+	 */
+	cp = optarg;
+	if (c == '?') {
+		/* If ":optstring" and unknown option is seen,
+		 * it is stored to OPTARG.
+		 */
+		if (optstring[1] == ':') {
+			cbuf[0] = optopt;
+			cp = cbuf;
+		}
+	}
+	if (cp)
+		set_local_var_from_halves("OPTARG", cp);
+	else
+		unset_local_var("OPTARG");
+
+	/* Convert -1 to "?" */
+	exitcode = EXIT_SUCCESS;
+	if (c < 0) { /* -1: end of options */
+		exitcode = EXIT_FAILURE;
+		c = '?';
+	}
+
+	/* Set VAR */
+	cbuf[0] = c;
+	set_local_var_from_halves(var, cbuf);
+
+	return exitcode;
+}
+#endif
+
+static int FAST_FUNC builtin_source(char **argv)
+{
+	char *arg_path, *filename;
+	HFILE *input;
+	save_arg_t sv;
+	char *args_need_save;
+#if ENABLE_HUSH_FUNCTIONS
+	smallint sv_flg;
+#endif
+
+	argv = skip_dash_dash(argv);
+	filename = argv[0];
+	if (!filename) {
+		/* bash says: "bash: .: filename argument required" */
+		return 2; /* bash compat */
+	}
+	arg_path = NULL;
+	if (!strchr(filename, '/')) {
+		arg_path = find_in_path(filename);
+		if (arg_path)
+			filename = arg_path;
+		else if (!ENABLE_HUSH_BASH_SOURCE_CURDIR) {
+			errno = ENOENT;
+			bb_simple_perror_msg(filename);
+			return EXIT_FAILURE;
+		}
+	}
+	input = hfopen(filename);
+	free(arg_path);
+	if (!input) {
+		bb_perror_msg("%s", filename);
+		/* POSIX: non-interactive shell should abort here,
+		 * not merely fail. So far no one complained :)
+		 */
+		return EXIT_FAILURE;
+	}
+
+#if ENABLE_HUSH_FUNCTIONS
+	sv_flg = G_flag_return_in_progress;
+	/* "we are inside sourced file, ok to use return" */
+	G_flag_return_in_progress = -1;
+#endif
+	args_need_save = argv[1]; /* used as a boolean variable */
+	if (args_need_save)
+		save_and_replace_G_args(&sv, argv);
+
+	/* "false; . ./empty_line; echo Zero:$?" should print 0 */
+	G.last_exitcode = 0;
+	parse_and_run_file(input);
+	hfclose(input);
+
+	if (args_need_save) /* can't use argv[1] instead: "shift" can mangle it */
+		restore_G_args(&sv, argv);
+#if ENABLE_HUSH_FUNCTIONS
+	G_flag_return_in_progress = sv_flg;
+#endif
+
+	return G.last_exitcode;
+}
+
+#if ENABLE_HUSH_TRAP
+static int FAST_FUNC builtin_trap(char **argv)
+{
+	int sig;
+	char *new_cmd;
+
+	if (!G_traps)
+		G_traps = xzalloc(sizeof(G_traps[0]) * NSIG);
+
+	argv++;
+	if (!*argv) {
+		int i;
+		/* No args: print all trapped */
+		for (i = 0; i < NSIG; ++i) {
+			if (G_traps[i]) {
+				printf("trap -- ");
+				print_escaped(G_traps[i]);
+				/* note: bash adds "SIG", but only if invoked
+				 * as "bash". If called as "sh", or if set -o posix,
+				 * then it prints short signal names.
+				 * We are printing short names: */
+				printf(" %s\n", get_signame(i));
+			}
+		}
+		/*fflush_all(); - done after each builtin anyway */
+		return EXIT_SUCCESS;
+	}
+
+	new_cmd = NULL;
+	/* If first arg is a number: reset all specified signals */
+	sig = bb_strtou(*argv, NULL, 10);
+	if (errno == 0) {
+		int ret;
+ process_sig_list:
+		ret = EXIT_SUCCESS;
+		while (*argv) {
+			sighandler_t handler;
+
+			sig = get_signum(*argv++);
+			if (sig < 0) {
+				ret = EXIT_FAILURE;
+				/* Mimic bash message exactly */
+				bb_error_msg("trap: %s: invalid signal specification", argv[-1]);
+				continue;
+			}
+
+			free(G_traps[sig]);
+			G_traps[sig] = xstrdup(new_cmd);
+
+			debug_printf("trap: setting SIG%s (%i) to '%s'\n",
+				get_signame(sig), sig, G_traps[sig]);
+
+			/* There is no signal for 0 (EXIT) */
+			if (sig == 0)
+				continue;
+
+			if (new_cmd)
+				handler = (new_cmd[0] ? record_pending_signo : SIG_IGN);
+			else
+				/* We are removing trap handler */
+				handler = pick_sighandler(sig);
+			install_sighandler(sig, handler);
+		}
+		return ret;
+	}
+
+	if (!argv[1]) { /* no second arg */
+		bb_simple_error_msg("trap: invalid arguments");
+		return EXIT_FAILURE;
+	}
+
+	/* First arg is "-": reset all specified to default */
+	/* First arg is "--": skip it, the rest is "handler SIGs..." */
+	/* Everything else: set arg as signal handler
+	 * (includes "" case, which ignores signal) */
+	if (argv[0][0] == '-') {
+		if (argv[0][1] == '\0') { /* "-" */
+			/* new_cmd remains NULL: "reset these sigs" */
+			goto reset_traps;
+		}
+		if (argv[0][1] == '-' && argv[0][2] == '\0') { /* "--" */
+			argv++;
+		}
+		/* else: "-something", no special meaning */
+	}
+	new_cmd = *argv;
+ reset_traps:
+	argv++;
+	goto process_sig_list;
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+static struct pipe *parse_jobspec(const char *str)
+{
+	struct pipe *pi;
+	unsigned jobnum;
+
+	if (sscanf(str, "%%%u", &jobnum) != 1) {
+		if (str[0] != '%'
+		 || (str[1] != '%' && str[1] != '+' && str[1] != '\0')
+		) {
+			bb_error_msg("bad argument '%s'", str);
+			return NULL;
+		}
+		/* It is "%%", "%+" or "%" - current job */
+		jobnum = G.last_jobid;
+		if (jobnum == 0) {
+			bb_simple_error_msg("no current job");
+			return NULL;
+		}
+	}
+	for (pi = G.job_list; pi; pi = pi->next) {
+		if (pi->jobid == jobnum) {
+			return pi;
+		}
+	}
+	bb_error_msg("%u: no such job", jobnum);
+	return NULL;
+}
+
+static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
+{
+	struct pipe *job;
+	const char *status_string;
+
+	checkjobs(NULL, 0 /*(no pid to wait for)*/);
+	for (job = G.job_list; job; job = job->next) {
+		if (job->alive_cmds == job->stopped_cmds)
+			status_string = "Stopped";
+		else
+			status_string = "Running";
+
+		printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+	}
+
+	clean_up_last_dead_job();
+
+	return EXIT_SUCCESS;
+}
+
+/* built-in 'fg' and 'bg' handler */
+static int FAST_FUNC builtin_fg_bg(char **argv)
+{
+	int i;
+	struct pipe *pi;
+
+	if (!G_interactive_fd)
+		return EXIT_FAILURE;
+
+	/* If they gave us no args, assume they want the last backgrounded task */
+	if (!argv[1]) {
+		for (pi = G.job_list; pi; pi = pi->next) {
+			if (pi->jobid == G.last_jobid) {
+				goto found;
+			}
+		}
+		bb_error_msg("%s: no current job", argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	pi = parse_jobspec(argv[1]);
+	if (!pi)
+		return EXIT_FAILURE;
+ found:
+	/* TODO: bash prints a string representation
+	 * of job being foregrounded (like "sleep 1 | cat") */
+	if (argv[0][0] == 'f' && G_saved_tty_pgrp) {
+		/* Put the job into the foreground.  */
+		tcsetpgrp(G_interactive_fd, pi->pgrp);
+	}
+
+	/* Restart the processes in the job */
+	debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
+	for (i = 0; i < pi->num_cmds; i++) {
+		debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
+	}
+	pi->stopped_cmds = 0;
+
+	i = kill(- pi->pgrp, SIGCONT);
+	if (i < 0) {
+		if (errno == ESRCH) {
+			delete_finished_job(pi);
+			return EXIT_SUCCESS;
+		}
+		bb_simple_perror_msg("kill (SIGCONT)");
+	}
+
+	if (argv[0][0] == 'f') {
+		remove_job_from_table(pi); /* FG job shouldn't be in job table */
+		return checkjobs_and_fg_shell(pi);
+	}
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_KILL
+static int FAST_FUNC builtin_kill(char **argv)
+{
+	int ret = 0;
+
+# if ENABLE_HUSH_JOB
+	if (argv[1] && strcmp(argv[1], "-l") != 0) {
+		int i = 1;
+
+		do {
+			struct pipe *pi;
+			char *dst;
+			int j, n;
+
+			if (argv[i][0] != '%')
+				continue;
+			/*
+			 * "kill %N" - job kill
+			 * Converting to pgrp / pid kill
+			 */
+			pi = parse_jobspec(argv[i]);
+			if (!pi) {
+				/* Eat bad jobspec */
+				j = i;
+				do {
+					j++;
+					argv[j - 1] = argv[j];
+				} while (argv[j]);
+				ret = 1;
+				i--;
+				continue;
+			}
+			/*
+			 * In jobs started under job control, we signal
+			 * entire process group by kill -PGRP_ID.
+			 * This happens, f.e., in interactive shell.
+			 *
+			 * Otherwise, we signal each child via
+			 * kill PID1 PID2 PID3.
+			 * Testcases:
+			 * sh -c 'sleep 1|sleep 1 & kill %1'
+			 * sh -c 'true|sleep 2 & sleep 1; kill %1'
+			 * sh -c 'true|sleep 1 & sleep 2; kill %1'
+			 */
+			n = G_interactive_fd ? 1 : pi->num_cmds;
+			dst = alloca(n * sizeof(int)*4);
+			argv[i] = dst;
+			if (G_interactive_fd)
+				dst += sprintf(dst, " -%u", (int)pi->pgrp);
+			else for (j = 0; j < n; j++) {
+				struct command *cmd = &pi->cmds[j];
+				/* Skip exited members of the job */
+				if (cmd->pid == 0)
+					continue;
+				/*
+				 * kill_main has matching code to expect
+				 * leading space. Needed to not confuse
+				 * negative pids with "kill -SIGNAL_NO" syntax
+				 */
+				dst += sprintf(dst, " %u", (int)cmd->pid);
+			}
+			*dst = '\0';
+		} while (argv[++i]);
+	}
+# endif
+
+	if (argv[1] || ret == 0) {
+		ret = run_applet_main(argv, kill_main);
+	}
+	/* else: ret = 1, "kill %bad_jobspec" case */
+	return ret;
+}
+#endif
+
+#if ENABLE_HUSH_WAIT
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
+# if !ENABLE_HUSH_JOB
+#  define wait_for_child_or_signal(pipe,pid) wait_for_child_or_signal(pid)
+# endif
+static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid)
+{
+	int ret = 0;
+	for (;;) {
+		int sig;
+		sigset_t oldset;
+
+		if (!sigisemptyset(&G.pending_set))
+			goto check_sig;
+
+		/* waitpid is not interruptible by SA_RESTARTed
+		 * signals which we use. Thus, this ugly dance:
+		 */
+
+		/* Make sure possible SIGCHLD is stored in kernel's
+		 * pending signal mask before we call waitpid.
+		 * Or else we may race with SIGCHLD, lose it,
+		 * and get stuck in sigsuspend...
+		 */
+		sigfillset(&oldset); /* block all signals, remember old set */
+		sigprocmask2(SIG_SETMASK, &oldset);
+
+		if (!sigisemptyset(&G.pending_set)) {
+			/* Crap! we raced with some signal! */
+			goto restore;
+		}
+
+		/*errno = 0; - checkjobs does this */
+/* Can't pass waitfor_pipe into checkjobs(): it won't be interruptible */
+		ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */
+		debug_printf_exec("checkjobs:%d\n", ret);
+# if ENABLE_HUSH_JOB
+		if (waitfor_pipe) {
+			int rcode = job_exited_or_stopped(waitfor_pipe);
+			debug_printf_exec("job_exited_or_stopped:%d\n", rcode);
+			if (rcode >= 0) {
+				ret = rcode;
+				sigprocmask(SIG_SETMASK, &oldset, NULL);
+				break;
+			}
+		}
+# endif
+		/* if ECHILD, there are no children (ret is -1 or 0) */
+		/* if ret == 0, no children changed state */
+		/* if ret != 0, it's exitcode+1 of exited waitfor_pid child */
+		if (errno == ECHILD || ret) {
+			ret--;
+			if (ret < 0) /* if ECHILD, may need to fix "ret" */
+				ret = 0;
+# if ENABLE_HUSH_BASH_COMPAT
+			if (waitfor_pid == -1 && errno == ECHILD) {
+				/* exitcode of "wait -n" with no children is 127, not 0 */
+				ret = 127;
+			}
+# endif
+			sigprocmask(SIG_SETMASK, &oldset, NULL);
+			break;
+		}
+		/* Wait for SIGCHLD or any other signal */
+		/* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
+		/* Note: sigsuspend invokes signal handler */
+		sigsuspend(&oldset);
+		/* ^^^ add "sigdelset(&oldset, SIGCHLD)" before sigsuspend
+		 * to make sure SIGCHLD is not masked off?
+		 * It was reported that this:
+		 *	fn() { : | return; }
+		 *	shopt -s lastpipe
+		 *	fn
+		 *	exec hush SCRIPT
+		 * under bash 4.4.23 runs SCRIPT with SIGCHLD masked,
+		 * making "wait" commands in SCRIPT block forever.
+		 */
+ restore:
+		sigprocmask(SIG_SETMASK, &oldset, NULL);
+ check_sig:
+		/* So, did we get a signal? */
+		sig = check_and_run_traps();
+		if (sig /*&& sig != SIGCHLD - always true */) {
+			/* Do this for any (non-ignored) signal, not only for ^C */
+			ret = 128 | sig;
+			break;
+		}
+		/* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */
+	}
+	return ret;
+}
+
+static int FAST_FUNC builtin_wait(char **argv)
+{
+	int ret;
+	int status;
+
+	argv = skip_dash_dash(argv);
+# if ENABLE_HUSH_BASH_COMPAT
+	if (argv[0] && strcmp(argv[0], "-n") == 0) {
+		/* wait -n */
+		/* (bash accepts "wait -n PID" too and ignores PID) */
+		G.dead_job_exitcode = -1;
+		return wait_for_child_or_signal(NULL, -1 /*no job, wait for one job*/);
+	}
+# endif
+	if (argv[0] == NULL) {
+		/* Don't care about wait results */
+		/* Note 1: must wait until there are no more children */
+		/* Note 2: must be interruptible */
+		/* Examples:
+		 * $ sleep 3 & sleep 6 & wait
+		 * [1] 30934 sleep 3
+		 * [2] 30935 sleep 6
+		 * [1] Done                   sleep 3
+		 * [2] Done                   sleep 6
+		 * $ sleep 3 & sleep 6 & wait
+		 * [1] 30936 sleep 3
+		 * [2] 30937 sleep 6
+		 * [1] Done                   sleep 3
+		 * ^C <-- after ~4 sec from keyboard
+		 * $
+		 */
+		return wait_for_child_or_signal(NULL, 0 /*no job and no pid to wait for*/);
+	}
+
+	do {
+		pid_t pid = bb_strtou(*argv, NULL, 10);
+		if (errno || pid <= 0) {
+# if ENABLE_HUSH_JOB
+			if (argv[0][0] == '%') {
+				struct pipe *wait_pipe;
+				ret = 127; /* bash compat for bad jobspecs */
+				wait_pipe = parse_jobspec(*argv);
+				if (wait_pipe) {
+					ret = job_exited_or_stopped(wait_pipe);
+					if (ret < 0) {
+						ret = wait_for_child_or_signal(wait_pipe, 0);
+					} else {
+						/* waiting on "last dead job" removes it */
+						clean_up_last_dead_job();
+					}
+				}
+				/* else: parse_jobspec() already emitted error msg */
+				continue;
+			}
+# endif
+			/* mimic bash message */
+			bb_error_msg("wait: '%s': not a pid or valid job spec", *argv);
+			ret = EXIT_FAILURE;
+			continue; /* bash checks all argv[] */
+		}
+
+		/* Do we have such child? */
+		ret = waitpid(pid, &status, WNOHANG);
+		if (ret < 0) {
+			/* No */
+			ret = 127;
+			if (errno == ECHILD) {
+				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.
+					 * In dash, it is 127.
+					 */
+					ret = G.last_bg_pid_exitcode;
+				} else {
+					/* Example: "wait 1". mimic bash message */
+					bb_error_msg("wait: pid %u is not a child of this shell", (unsigned)pid);
+				}
+			} else {
+				/* ??? */
+				bb_perror_msg("wait %s", *argv);
+			}
+			continue; /* bash checks all argv[] */
+		}
+		if (ret == 0) {
+			/* Yes, and it still runs */
+			ret = wait_for_child_or_signal(NULL, pid);
+		} else {
+			/* Yes, and it just exited */
+			process_wait_result(NULL, pid, status);
+			ret = WEXITSTATUS(status);
+			if (WIFSIGNALED(status))
+				ret = 128 | WTERMSIG(status);
+		}
+	} while (*++argv);
+
+	return ret;
+}
+#endif
+
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_FUNCTIONS
+static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min)
+{
+	if (argv[1]) {
+		def = bb_strtou(argv[1], NULL, 10);
+		if (errno || def < def_min || argv[2]) {
+			bb_error_msg("%s: bad arguments", argv[0]);
+			def = UINT_MAX;
+		}
+	}
+	return def;
+}
+#endif
+
+#if ENABLE_HUSH_LOOPS
+static int FAST_FUNC builtin_break(char **argv)
+{
+	unsigned depth;
+	if (G.depth_of_loop == 0) {
+		bb_error_msg("%s: only meaningful in a loop", argv[0]);
+		/* if we came from builtin_continue(), need to undo "= 1" */
+		G.flag_break_continue = 0;
+		return EXIT_SUCCESS; /* bash compat */
+	}
+	G.flag_break_continue++; /* BC_BREAK = 1, or BC_CONTINUE = 2 */
+
+	G.depth_break_continue = depth = parse_numeric_argv1(argv, 1, 1);
+	if (depth == UINT_MAX)
+		G.flag_break_continue = BC_BREAK;
+	if (G.depth_of_loop < depth)
+		G.depth_break_continue = G.depth_of_loop;
+
+	return EXIT_SUCCESS;
+}
+
+static int FAST_FUNC builtin_continue(char **argv)
+{
+	G.flag_break_continue = 1; /* BC_CONTINUE = 2 = 1+1 */
+	return builtin_break(argv);
+}
+#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+static int FAST_FUNC builtin_return(char **argv)
+{
+	int rc;
+
+	if (G_flag_return_in_progress != -1) {
+		bb_error_msg("%s: not in a function or sourced script", argv[0]);
+		return EXIT_FAILURE; /* bash compat */
+	}
+
+	G_flag_return_in_progress = 1;
+
+	/* bash:
+	 * out of range: wraps around at 256, does not error out
+	 * non-numeric param:
+	 * f() { false; return qwe; }; f; echo $?
+	 * bash: return: qwe: numeric argument required  <== we do this
+	 * 255  <== we also do this
+	 */
+	rc = parse_numeric_argv1(argv, G.last_exitcode, 0);
+# if ENABLE_HUSH_TRAP
+	if (argv[1]) { /* "return ARG" inside a running trap sets $? */
+		debug_printf_exec("G.return_exitcode=%d\n", rc);
+		G.return_exitcode = rc;
+	}
+# endif
+	return rc;
+}
+#endif
+
+#if ENABLE_HUSH_TIMES
+static int FAST_FUNC builtin_times(char **argv UNUSED_PARAM)
+{
+	static const uint8_t times_tbl[] ALIGN1 = {
+		' ',  offsetof(struct tms, tms_utime),
+		'\n', offsetof(struct tms, tms_stime),
+		' ',  offsetof(struct tms, tms_cutime),
+		'\n', offsetof(struct tms, tms_cstime),
+		0
+	};
+	const uint8_t *p;
+	unsigned clk_tck;
+	struct tms buf;
+
+	clk_tck = bb_clk_tck();
+
+	times(&buf);
+	p = times_tbl;
+	do {
+		unsigned sec, frac;
+		unsigned long t;
+		t = *(clock_t *)(((char *) &buf) + p[1]);
+		sec = t / clk_tck;
+		frac = t % clk_tck;
+		printf("%um%u.%03us%c",
+			sec / 60, sec % 60,
+			(frac * 1000) / clk_tck,
+			p[0]);
+		p += 2;
+	} while (*p);
+
+	return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_MEMLEAK
+static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
+{
+	void *p;
+	unsigned long l;
+
+# ifdef M_TRIM_THRESHOLD
+	/* Optional. Reduces probability of false positives */
+	malloc_trim(0);
+# endif
+	/* Crude attempt to find where "free memory" starts,
+	 * sans fragmentation. */
+	p = malloc(240);
+	l = (unsigned long)p;
+	free(p);
+	p = malloc(3400);
+	if (l < (unsigned long)p) l = (unsigned long)p;
+	free(p);
+
+
+# if 0  /* debug */
+	{
+		struct mallinfo mi = mallinfo();
+		printf("top alloc:0x%lx malloced:%d+%d=%d\n", l,
+			mi.arena, mi.hblkhd, mi.arena + mi.hblkhd);
+	}
+# endif
+
+	if (!G.memleak_value)
+		G.memleak_value = l;
+
+	l -= G.memleak_value;
+	if ((long)l < 0)
+		l = 0;
+	l /= 1024;
+	if (l > 127)
+		l = 127;
+
+	/* Exitcode is "how many kilobytes we leaked since 1st call" */
+	return l;
+}
+#endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 08/23] cli: Port Busybox 2021 hush to U-Boot
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (6 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 07/23] cli: Add Busybox upstream hush.c file Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-03-30 19:46 ` [RFC PATCH v7 09/23] cli: Add menu for hush parser Francis Laniel
                   ` (14 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Alexey Romanov, Patrice Chotard,
	Patrick Delaunay, Fabrice Gasnier, Marek Vasut, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Rui Miguel Silva,
	Linus Walleij, Andrew Scull

Adds new file cli_hush_2021.c, it is a copy of Busybox hush file as it was of
time to commit 37460f5da.
This commit modifies Busybox hush to not compile some part specific to Busybox
and adds some code needed by U-Boot.
The modifications consists mainly on adding code #if(n)def guards.

For the moment, this refurbished flavor of hush only permits running command
without any keywords (i.e., if and for are not recognized) or variable expansion
(i.e., echo $foo prints foo and not value stored in variable foo).

A new file was also added to define some functions specific to U-Boot.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Signed-off-by: Harald Seiler <hws@denx.de>
---
 common/cli_hush_2021.c     | 274 ++++++++++++++++++++
 common/cli_hush_upstream.c | 501 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 774 insertions(+), 1 deletion(-)
 create mode 100644 common/cli_hush_2021.c

diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
new file mode 100644
index 0000000000..6d109933b8
--- /dev/null
+++ b/common/cli_hush_2021.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * This file defines the compilation unit for the new hush shell version.  The
+ * actual implementation from upstream BusyBox can be found in
+ * `cli_hush_2021_upstream.c` which is included at the end of this file.
+ *
+ * This "wrapper" technique is used to keep the changes to the upstream version
+ * as minmal as possible.  Instead, all defines and redefines necessary are done
+ * here, outside the upstream sources.  This will hopefully make upgrades to
+ * newer revisions much easier.
+ *
+ * Copyright (c) 2021, Harald Seiler, DENX Software Engineering, hws@denx.de
+ */
+
+#include <common.h>         /* readline */
+#include <env.h>
+#include <malloc.h>         /* malloc, free, realloc*/
+#include <linux/ctype.h>    /* isalpha, isdigit */
+#include <console.h>
+#include <bootretry.h>
+#include <cli.h>
+#include <cli_hush.h>
+#include <command.h>        /* find_cmd */
+#include <asm/global_data.h>
+
+/*
+ * BusyBox Version: UPDATE THIS WHEN PULLING NEW UPSTREAM REVISION!
+ */
+#define BB_VER			"1.34.0.git37460f5daff9"
+
+/*
+ * Define hush features by the names used upstream.
+ */
+#define ENABLE_HUSH_INTERACTIVE	1
+#define ENABLE_FEATURE_EDITING	1
+/* No MMU in U-Boot */
+#define BB_MMU			0
+#define USE_FOR_NOMMU(...)	__VA_ARGS__
+#define USE_FOR_MMU(...)
+
+/*
+ * Size-saving "small" ints (arch-dependent)
+ */
+#if CONFIG_IS_ENABLED(X86) || CONFIG_IS_ENABLED(X86_64) || CONFIG_IS_ENABLED(MIPS)
+/* add other arches which benefit from this... */
+typedef signed char smallint;
+typedef unsigned char smalluint;
+#else
+/* for arches where byte accesses generate larger code: */
+typedef int smallint;
+typedef unsigned smalluint;
+#endif
+
+/*
+ * Alignment defines used by BusyBox.
+ */
+#define ALIGN1			__attribute__((aligned(1)))
+#define ALIGN2			__attribute__((aligned(2)))
+#define ALIGN4			__attribute__((aligned(4)))
+#define ALIGN8			__attribute__((aligned(8)))
+#define ALIGN_PTR		__attribute__((aligned(sizeof(void*))))
+
+/*
+ * Miscellaneous compiler/platform defines.
+ */
+#define FAST_FUNC /* not used in U-Boot */
+#define UNUSED_PARAM		__always_unused
+#define ALWAYS_INLINE		__always_inline
+#define NOINLINE		noinline
+
+/*
+ * Defines to provide equivalents to what libc/BusyBox defines.
+ */
+#define EOF			(-1)
+#define EXIT_SUCCESS		0
+#define EXIT_FAILURE		1
+
+/*
+ * Stubs to provide libc/BusyBox functions based on U-Boot equivalents where it
+ * makes sense.
+ */
+#define utoa			simple_itoa
+
+static void __noreturn xfunc_die(void)
+{
+	panic("HUSH died!");
+}
+
+#define bb_error_msg_and_die(format, ...) do { \
+panic("HUSH: " format, __VA_ARGS__); \
+} while (0);
+
+#define bb_simple_error_msg_and_die(msg) do { \
+panic_str("HUSH: " msg); \
+} while (0);
+
+/* fdprintf() is used for debug output. */
+static int __maybe_unused fdprintf(int fd, const char *format, ...)
+{
+	va_list args;
+	uint i;
+
+	assert(fd == 2);
+
+	va_start(args, format);
+	i = vprintf(format, args);
+	va_end(args);
+
+	return i;
+}
+
+static void bb_verror_msg(const char *s, va_list p, const char* strerr)
+{
+	/* TODO: what to do with strerr arg? */
+	vprintf(s, p);
+}
+
+static void bb_error_msg(const char *s, ...)
+{
+	va_list p;
+
+	va_start(p, s);
+	bb_verror_msg(s, p, NULL);
+	va_end(p);
+}
+
+static void *xmalloc(size_t size)
+{
+	void *p = NULL;
+	if (!(p = malloc(size)))
+		panic("out of memory");
+	return p;
+}
+
+static void *xzalloc(size_t size)
+{
+	void *p = xmalloc(size);
+	memset(p, 0, size);
+	return p;
+}
+
+static void *xrealloc(void *ptr, size_t size)
+{
+	void *p = NULL;
+	if (!(p = realloc(ptr, size)))
+		panic("out of memory");
+	return p;
+}
+
+#define xstrdup		strdup
+#define xstrndup	strndup
+
+static void *mempcpy(void *dest, const void *src, size_t len)
+{
+	return memcpy(dest, src, len) + len;
+}
+
+/* Like strcpy but can copy overlapping strings. */
+static void overlapping_strcpy(char *dst, const char *src)
+{
+	/*
+	 * Cheap optimization for dst == src case -
+	 * better to have it here than in many callers.
+	 */
+	if (dst != src) {
+		while ((*dst = *src) != '\0') {
+			dst++;
+			src++;
+		}
+	}
+}
+
+static char* skip_whitespace(const char *s)
+{
+	/*
+	 * In POSIX/C locale (the only locale we care about: do we REALLY want
+	 * to allow Unicode whitespace in, say, .conf files? nuts!)
+	 * isspace is only these chars: "\t\n\v\f\r" and space.
+	 * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13.
+	 * Use that.
+	 */
+	while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9))
+		s++;
+
+	return (char *) s;
+}
+
+static char* skip_non_whitespace(const char *s)
+{
+	while (*s != '\0' && *s != ' ' && (unsigned char)(*s - 9) > (13 - 9))
+		s++;
+
+	return (char *) s;
+}
+
+#define is_name(c)	((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)	((c) == '_' || isalnum((unsigned char)(c)))
+
+static const char* endofname(const char *name)
+{
+	if (!is_name(*name))
+		return name;
+	while (*++name) {
+		if (!is_in_name(*name))
+			break;
+	}
+	return name;
+}
+
+struct in_str;
+static int u_boot_cli_readline(struct in_str *i);
+
+struct in_str;
+static int u_boot_cli_readline(struct in_str *i);
+
+/*
+ * BusyBox globals which are needed for hush.
+ */
+static uint8_t xfunc_error_retval;
+
+static const char defifsvar[] __aligned(1) = "IFS= \t\n";
+#define defifs (defifsvar + 4)
+
+/*
+ * This define is used for changes that need be done directly in the upstream
+ * sources still. Ideally, its use should be minimized as much as possible.
+ */
+#define __U_BOOT__
+
+/*
+ *
+ * +-- Include of the upstream sources --+ *
+ * V                                     V
+ */
+#include "cli_hush_upstream.c"
+/*
+ * A                                     A
+ * +-- Include of the upstream sources --+ *
+ *
+ */
+
+int u_boot_hush_start_2021(void)
+{
+	INIT_G();
+	return 0;
+}
+
+static int u_boot_cli_readline(struct in_str *i)
+{
+	char *prompt;
+	char __maybe_unused *ps_prompt = NULL;
+
+	if (!G.promptmode)
+		prompt = CONFIG_SYS_PROMPT;
+#ifdef CONFIG_SYS_PROMPT_HUSH_PS2
+	else
+		prompt = CONFIG_SYS_PROMPT_HUSH_PS2;
+#else
+	/* TODO: default value? */
+	#error "SYS_PROMPT_HUSH_PS2 is not defined!"
+#endif
+
+	if (CONFIG_IS_ENABLED(CMDLINE_PS_SUPPORT)) {
+		if (!G.promptmode)
+			ps_prompt = env_get("PS1");
+		else
+			ps_prompt = env_get("PS2");
+
+		if (ps_prompt)
+			prompt = ps_prompt;
+	}
+
+	return cli_readline(prompt);
+}
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index c970d9097e..cc64af4e0c 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -343,6 +343,7 @@
 //usage:#define hush_full_usage "\n\n"
 //usage:	"Unix shell interpreter"
 
+#ifndef __U_BOOT__
 #if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
 	|| defined(__APPLE__) \
     )
@@ -378,6 +379,7 @@
 #else
 # define NUM_SCRIPTS 0
 #endif
+#endif /* !__U_BOOT__ */
 
 /* So far, all bash compat is controlled by one config option */
 /* Separate defines document which part of code implements what */
@@ -421,6 +423,7 @@
 # define USE_FOR_MMU(...)
 #endif
 
+#ifndef __U_BOOT__
 #include "NUM_APPLETS.h"
 #if NUM_APPLETS == 1
 /* STANDALONE does not make sense, and won't compile */
@@ -432,6 +435,7 @@
 # define IF_FEATURE_SH_STANDALONE(...)
 # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
 #endif
+#endif /* __U_BOOT__ */
 
 #if !ENABLE_HUSH_INTERACTIVE
 # undef ENABLE_FEATURE_EDITING
@@ -477,8 +481,13 @@
 #define JOB_STATUS_FORMAT    "[%u] %-22s %.40s\n"
 
 #define _SPECIAL_VARS_STR     "_*@$!?#-"
+#ifndef __U_BOOT__
 #define SPECIAL_VARS_STR     ("_*@$!?#-" + 1)
 #define NUMERIC_SPECVARS_STR ("_*@$!?#-" + 3)
+#else /* __U_BOOT__ */
+#define SPECIAL_VARS_STR     "*@$!?#-"
+#define NUMERIC_SPECVARS_STR "$!?#-"
+#endif /* __U_BOOT__ */
 #if BASH_PATTERN_SUBST
 /* Support / and // replace ops */
 /* Note that // is stored as \ in "encoded" string representation */
@@ -511,6 +520,7 @@ typedef struct nommu_save_t {
 } nommu_save_t;
 #endif
 
+
 enum {
 	RES_NONE  = 0,
 #if ENABLE_HUSH_IF
@@ -594,6 +604,7 @@ typedef struct in_str {
 	HFILE *file;
 } in_str;
 
+#ifndef __U_BOOT__
 /* The descrip member of this structure is only used to make
  * debugging output pretty */
 static const struct {
@@ -640,9 +651,12 @@ typedef enum redir_type {
 	HEREDOC_QUOTED   = 2,
 } redir_type;
 
+#endif /* !__U_BOOT__ */
 
 struct command {
+#ifndef __U_BOOT__
 	pid_t pid;                  /* 0 if exited */
+#endif /* !__U_BOOT__ */
 	unsigned assignment_cnt;    /* how many argv[i] are assignments? */
 #if ENABLE_HUSH_LINENO_VAR
 	unsigned lineno;
@@ -684,6 +698,9 @@ struct command {
  * When command is freed, it severs the link
  * (sets ->child_func->parent_cmd to NULL).
  */
+#endif
+#ifdef __U_BOOT__
+	int argc; /* number of program arguments */
 #endif
 	char **argv;                /* command name and arguments */
 /* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
@@ -692,15 +709,23 @@ struct command {
  * Example: argv[0]=='.^C*^C.' here: echo .$*.
  * References of the form ^C`cmd arg^C are `cmd arg` substitutions.
  */
+#ifndef __U_BOOT__
 	struct redir_struct *redirects; /* I/O redirections */
+#endif /* !__U_BOOT__ */
 };
 /* Is there anything in this command at all? */
+#ifndef __U_BOOT__
 #define IS_NULL_CMD(cmd) \
 	(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
 
+#else /* __U_BOOT__ */
+#define IS_NULL_CMD(cmd) \
+	(!(cmd)->group && !(cmd)->argv)
+#endif /* __U_BOOT__ */
 struct pipe {
 	struct pipe *next;
 	int num_cmds;               /* total number of commands in pipe */
+#ifndef __U_BOOT__
 	int alive_cmds;             /* number of commands running (not exited) */
 	int stopped_cmds;           /* number of commands alive, but stopped */
 #if ENABLE_HUSH_JOB
@@ -708,6 +733,7 @@ struct pipe {
 	pid_t pgrp;                 /* process group ID for the job */
 	char *cmdtext;              /* name of job */
 #endif
+#endif /* !__U_BOOT__ */
 	struct command *cmds;       /* array of commands in pipe */
 	smallint followup;          /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
 	IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
@@ -731,8 +757,10 @@ struct parse_context {
 	struct pipe *pipe;
 	/* last command in pipe (being constructed right now) */
 	struct command *command;
+#ifndef __U_BOOT__
 	/* last redirect in command->redirects list */
 	struct redir_struct *pending_redirect;
+#endif /* !__U_BOOT__ */
 	o_string word;
 #if !BB_MMU
 	o_string as_string;
@@ -766,19 +794,23 @@ enum {
 	WORD_IS_KEYWORD       = 3,
 };
 
+#ifndef __U_BOOT__
 /* On program start, environ points to initial environment.
  * putenv adds new pointers into it, unsetenv removes them.
  * Neither of these (de)allocates the strings.
  * setenv allocates new strings in malloc space and does putenv,
  * and thus setenv is unusable (leaky) for shell's purposes */
 #define setenv(...) setenv_is_leaky_dont_use()
+#endif /* !__U_BOOT__ */
 struct variable {
 	struct variable *next;
 	char *varstr;        /* points to "name=" portion */
 	int max_len;         /* if > 0, name is part of initial env; else name is malloced */
+#ifndef __U_BOOT__
 	uint16_t var_nest_level;
 	smallint flg_export; /* putenv should be done on this var */
 	smallint flg_read_only;
+#endif /* !__U_BOOT__ */
 };
 
 enum {
@@ -850,6 +882,7 @@ enum {
 /* "Globals" within this file */
 /* Sorted roughly by size (smaller offsets == smaller code) */
 struct globals {
+#ifndef __U_BOOT__
 	/* interactive_fd != 0 means we are an interactive shell.
 	 * If we are, then saved_tty_pgrp can also be != 0, meaning
 	 * that controlling tty is available. With saved_tty_pgrp == 0,
@@ -870,6 +903,10 @@ struct globals {
 #else
 # define G_interactive_fd 0
 #endif
+#else /* __U_BOOT__ */
+# define G_interactive_fd 0
+#endif /* __U_BOOT__ */
+#ifndef __U_BOOT__
 #if ENABLE_FEATURE_EDITING
 	line_input_t *line_input_state;
 #endif
@@ -888,8 +925,10 @@ struct globals {
 #else
 # define G_saved_tty_pgrp 0
 #endif
+#endif /* !__U_BOOT__ */
 	/* How deeply are we in context where "set -e" is ignored */
 	int errexit_depth;
+#ifndef __U_BOOT__
 	/* "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
@@ -915,13 +954,16 @@ struct globals {
 #endif
 	char opt_s;
 	char opt_c;
+#endif /* !__U_BOOT__ */
 #if ENABLE_HUSH_INTERACTIVE
 	smallint promptmode; /* 0: PS1, 1: PS2 */
 #endif
 	smallint flag_SIGINT;
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_LOOPS
 	smallint flag_break_continue;
 #endif
+#endif /* !__U_BOOT__ */
 #if ENABLE_HUSH_FUNCTIONS
 	/* 0: outside of a function (or sourced file)
 	 * -1: inside of a function, ok to use return builtin
@@ -937,6 +979,7 @@ struct globals {
 	smalluint last_exitcode;
 	smalluint expand_exitcode;
 	smalluint last_bg_pid_exitcode;
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_SET
 	/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
 	smalluint global_args_malloced;
@@ -947,6 +990,7 @@ struct globals {
 #if ENABLE_HUSH_BASH_COMPAT
 	int dead_job_exitcode; /* for "wait -n" */
 #endif
+#endif /* !__U_BOOT__ */
 	/* how many non-NULL argv's we have. NB: $# + 1 */
 	int global_argc;
 	char **global_argv;
@@ -954,19 +998,30 @@ struct globals {
 	char *argv0_for_re_execing;
 #endif
 #if ENABLE_HUSH_LOOPS
+#ifndef __U_BOOT__
 	unsigned depth_break_continue;
+#endif /* !__U_BOOT__ */
 	unsigned depth_of_loop;
 #endif
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_GETOPTS
 	unsigned getopt_count;
 #endif
+#endif /* !__U_BOOT__ */
 	const char *ifs;
+#ifdef __U_BOOT__
+	int flag_repeat;
+	int do_repeat;
+#endif /* __U_BOOT__ */
 	char *ifs_whitespace; /* = G.ifs or malloced */
+#ifndef __U_BOOT__
 	const char *cwd;
+#endif /* !__U_BOOT__ */
 	struct variable *top_var;
 	char **expanded_assignments;
 	struct variable **shadowed_vars_pp;
 	unsigned var_nest_level;
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_FUNCTIONS
 # if ENABLE_HUSH_LOCAL
 	unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
@@ -1022,9 +1077,11 @@ struct globals {
 	int x_mode_fd;
 	o_string x_mode_buf;
 #endif
+#endif /* !__U_BOOT__ */
 #if HUSH_DEBUG >= 2
 	int debug_indent;
 #endif
+#ifndef __U_BOOT__
 	struct sigaction sa;
 	char optstring_buf[sizeof("eixcs")];
 #if BASH_EPOCH_VARS
@@ -1033,19 +1090,35 @@ struct globals {
 #if ENABLE_FEATURE_EDITING
 	char user_input_buf[CONFIG_FEATURE_EDITING_MAX_LEN];
 #endif
+#endif /* !__U_BOOT__ */
 };
+#ifdef __U_BOOT__
+struct globals *ptr_to_globals;
+#endif /* __U_BOOT__ */
 #define G (*ptr_to_globals)
 /* Not #defining name to G.name - this quickly gets unwieldy
  * (too many defines). Also, I actually prefer to see when a variable
  * is global, thus "G." prefix is a useful hint */
+#ifdef __U_BOOT__
+#define SET_PTR_TO_GLOBALS(x) do { \
+	(*(struct globals**)&ptr_to_globals) = (void*)(x); \
+	barrier(); \
+} while (0)
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	G.promptmode = 1; \
+} while (0)
+#else /* !__U_BOOT__ */
 #define INIT_G() do { \
 	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 	/* memset(&G.sa, 0, sizeof(G.sa)); */  \
 	sigfillset(&G.sa.sa_mask); \
 	G.sa.sa_flags = SA_RESTART; \
 } while (0)
+#endif /* !__U_BOOT__ */
 
 
+#ifndef __U_BOOT__
 /* Function prototypes for builtins */
 static int builtin_cd(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_ECHO
@@ -1245,6 +1318,7 @@ static const struct built_in_command bltins2[] ALIGN_PTR = {
 #endif
 };
 
+#endif /* !__U_BOOT__ */
 
 /* Debug printouts.
  */
@@ -1396,7 +1470,11 @@ static void die_if_script(void)
 	}
 }
 
+#ifdef __U_BOOT__
+static void __maybe_unused msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
+#else /* !__U_BOOT__ */
 static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
+#endif /* !__U_BOOT__ */
 {
 	va_list p;
 
@@ -1409,6 +1487,7 @@ static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
 	die_if_script();
 }
 
+#ifndef __U_BOOT__
 static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
 {
 	if (msg)
@@ -1417,6 +1496,7 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
 		bb_simple_error_msg("syntax error");
 	die_if_script();
 }
+#endif /* !__U_BOOT__ */
 
 static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
 {
@@ -1465,13 +1545,16 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
 # define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch)
 #endif
 
-
 /* Utility functions
  */
 /* Replace each \x with x in place, return ptr past NUL. */
 static char *unbackslash(char *src)
 {
+#ifdef __U_BOOT__
+	char *dst = src = (char *)strchrnul(src, '\\');
+#else /* !__U_BOOT__ */
 	char *dst = src = strchrnul(src, '\\');
+#endif /* !__U_BOOT__ */
 	while (1) {
 		if (*src == '\\') {
 			src++;
@@ -1539,6 +1622,7 @@ static char **add_string_to_strings(char **strings, char *add)
 	v[1] = NULL;
 	return add_strings_to_strings(strings, v, /*dup:*/ 0);
 }
+
 #if LEAK_HUNTING
 static char **xx_add_string_to_strings(int lineno, char **strings, char *add)
 {
@@ -1564,6 +1648,7 @@ static void free_strings(char **strings)
 	free(strings);
 }
 
+#ifndef __U_BOOT__
 static int dup_CLOEXEC(int fd, int avoid_fd)
 {
 	int newfd;
@@ -1749,6 +1834,7 @@ static int fd_in_HFILEs(int fd)
 	return 0;
 }
 
+#endif /* !__U_BOOT__ */
 
 /* Helpers for setting new $n and restoring them back
  */
@@ -1756,9 +1842,12 @@ typedef struct save_arg_t {
 	char *sv_argv0;
 	char **sv_g_argv;
 	int sv_g_argc;
+#ifndef __U_BOOT__
 	IF_HUSH_SET(smallint sv_g_malloced;)
+#endif /* !__U_BOOT__ */
 } save_arg_t;
 
+#ifndef __U_BOOT__
 static void save_and_replace_G_args(save_arg_t *sv, char **argv)
 {
 	sv->sv_argv0 = argv[0];
@@ -1789,8 +1878,10 @@ static void restore_G_args(save_arg_t *sv, char **argv)
 	G.global_argc = sv->sv_g_argc;
 	IF_HUSH_SET(G.global_args_malloced = sv->sv_g_malloced;)
 }
+#endif /* !__U_BOOT__ */
 
 
+#ifndef __U_BOOT__
 /* Basic theory of signal handling in shell
  * ========================================
  * This does not describe what hush does, rather, it is current understanding
@@ -1969,7 +2060,9 @@ static sighandler_t install_sighandler(int sig, sighandler_t handler)
 	sigaction(sig, &G.sa, &old_sa);
 	return old_sa.sa_handler;
 }
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 static void hush_exit(int exitcode) NORETURN;
 
 static void restore_ttypgrp_and__exit(void) NORETURN;
@@ -2039,7 +2132,9 @@ static void sigexit(int sig)
 # define enable_restore_tty_pgrp_on_exit()  ((void)0)
 
 #endif
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 static sighandler_t pick_sighandler(unsigned sig)
 {
 	sighandler_t handler = SIG_DFL;
@@ -2066,7 +2161,9 @@ static sighandler_t pick_sighandler(unsigned sig)
 	}
 	return handler;
 }
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 /* Restores tty foreground process group, and exits. */
 static void hush_exit(int exitcode)
 {
@@ -2226,6 +2323,7 @@ static const char *get_cwd(int force)
 	return G.cwd;
 }
 
+#endif /* !__U_BOOT__ */
 
 /*
  * Shell and environment variable support
@@ -2263,8 +2361,10 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
 	if (vpp)
 		return (*vpp)->varstr + len + 1;
 
+#ifndef __U_BOOT__
 	if (strcmp(name, "PPID") == 0)
 		return utoa(G.root_ppid);
+#endif /* !__U_BOOT__ */
 	// bash compat: UID? EUID?
 #if ENABLE_HUSH_RANDOM_SUPPORT
 	if (strcmp(name, "RANDOM") == 0)
@@ -2293,6 +2393,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
 	return NULL;
 }
 
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_GETOPTS
 static void handle_changed_special_names(const char *name, unsigned name_len)
 {
@@ -2307,15 +2408,25 @@ static void handle_changed_special_names(const char *name, unsigned name_len)
 /* Do not even bother evaluating arguments */
 # define handle_changed_special_names(...) ((void)0)
 #endif
+#else /* __U_BOOT__ */
+/* Do not even bother evaluating arguments */
+# define handle_changed_special_names(...) ((void)0)
+#endif /* __U_BOOT__ */
 
 /* str holds "NAME=VAL" and is expected to be malloced.
  * We take ownership of it.
  */
+#ifndef __U_BOOT__
 #define SETFLAG_EXPORT   (1 << 0)
 #define SETFLAG_UNEXPORT (1 << 1)
 #define SETFLAG_MAKE_RO  (1 << 2)
+#endif /* !__U_BOOT__ */
 #define SETFLAG_VARLVL_SHIFT   3
+#ifndef __U_BOOT__
 static int set_local_var(char *str, unsigned flags)
+#else /* __U_BOOT__ */
+int set_local_var_2021(char *str, int flags)
+#endif /* __U_BOOT__ */
 {
 	struct variable **cur_pp;
 	struct variable *cur;
@@ -2323,7 +2434,9 @@ static int set_local_var(char *str, unsigned flags)
 	char *eq_sign;
 	int name_len;
 	int retval;
+#ifndef __U_BOOT__
 	unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
+#endif /* !__U_BOOT__ */
 
 	eq_sign = strchr(str, '=');
 	if (HUSH_DEBUG && !eq_sign)
@@ -2337,6 +2450,7 @@ static int set_local_var(char *str, unsigned flags)
 			continue;
 		}
 
+#ifndef __U_BOOT__
 		/* We found an existing var with this name */
 		if (cur->flg_read_only) {
 			bb_error_msg("%s: readonly variable", str);
@@ -2389,6 +2503,7 @@ static int set_local_var(char *str, unsigned flags)
 			}
 			break;
 		}
+#endif /* !__U_BOOT__ */
 
 		if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
 			debug_printf_env("assignement '%s' does not change anything\n", str);
@@ -2420,15 +2535,22 @@ static int set_local_var(char *str, unsigned flags)
 	}
 
 	/* Not found or shadowed - create new variable struct */
+#ifndef __U_BOOT__
 	debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl);
+#else /* __U_BOOT__ */
+	debug_printf_env("%s: alloc new var '%s'\n", __func__, str);
+#endif /* __U_BOOT__ */
 	cur = xzalloc(sizeof(*cur));
+#ifndef __U_BOOT__
 	cur->var_nest_level = local_lvl;
+#endif /* !__U_BOOT__ */
 	cur->next = *cur_pp;
 	*cur_pp = cur;
 
  set_str_and_exp:
 	cur->varstr = str;
  exp:
+#ifndef __U_BOOT__
 #if !BB_MMU || ENABLE_HUSH_READONLY
 	if (flags & SETFLAG_MAKE_RO) {
 		cur->flg_read_only = 1;
@@ -2436,7 +2558,9 @@ static int set_local_var(char *str, unsigned flags)
 #endif
 	if (flags & SETFLAG_EXPORT)
 		cur->flg_export = 1;
+#endif /* !__U_BOOT__ */
 	retval = 0;
+#ifndef __U_BOOT__
 	if (cur->flg_export) {
 		if (flags & SETFLAG_UNEXPORT) {
 			cur->flg_export = 0;
@@ -2449,6 +2573,7 @@ static int set_local_var(char *str, unsigned flags)
 			 */
 		}
 	}
+#endif /* !__U_BOOT__ */
 	free(free_me);
 
 	handle_changed_special_names(cur->varstr, name_len - 1);
@@ -2456,6 +2581,7 @@ static int set_local_var(char *str, unsigned flags)
 	return retval;
 }
 
+#ifndef __U_BOOT__
 static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
 {
 	char *var = xasprintf("%s=%s", name, val);
@@ -2467,6 +2593,7 @@ static void set_pwd_var(unsigned flag)
 {
 	set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag);
 }
+#endif /* !__U_BOOT__ */
 
 #if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
 static int unset_local_var_len(const char *name, int name_len)
@@ -2509,6 +2636,7 @@ static int unset_local_var(const char *name)
 #endif
 
 
+#ifndef __U_BOOT__
 /*
  * Helpers for "var1=val1 var2=val2 cmd" feature
  */
@@ -2602,11 +2730,13 @@ static void reinit_unicode_for_hush(void)
 	}
 }
 
+#endif /* !__U_BOOT__ */
 /*
  * in_str support (strings, and "strings" read from files).
  */
 
 #if ENABLE_HUSH_INTERACTIVE
+#ifndef __U_BOOT__
 /* To test correct lineedit/interactive behavior, type from command line:
  *	echo $P\
  *	\
@@ -2639,8 +2769,15 @@ static const char *setup_prompt_string(void)
 	debug_printf("prompt_str '%s'\n", prompt_str);
 	return prompt_str;
 }
+#endif /* !__U_BOOT__ */
+
+#ifndef __U_BOOT__
 static int get_user_input(struct in_str *i)
+#else /* __U_BOOT__ */
+static void get_user_input(struct in_str *i)
+#endif /* __U_BOOT__ */
 {
+#ifndef __U_BOOT__
 # if ENABLE_FEATURE_EDITING
 	/* In EDITING case, this function reads next input line,
 	 * saves it in i->p, then returns 1st char of it.
@@ -2716,37 +2853,139 @@ static int get_user_input(struct in_str *i)
 	}
 	return r;
 # endif
+#else /* __U_BOOT__ */
+	int n;
+	int promptme;
+	static char the_command[CONFIG_SYS_CBSIZE + 1];
+
+	bootretry_reset_cmd_timeout();
+	promptme = 1;
+	n = u_boot_cli_readline(i);
+
+# ifdef CONFIG_BOOT_RETRY_TIME
+	if (n == -2) {
+		puts("\nTimeout waiting for command\n");
+#  ifdef CONFIG_RESET_TO_RETRY
+		do_reset(NULL, 0, 0, NULL);
+#  else
+#	error "This currently only works with CONFIG_RESET_TO_RETRY enabled"
+#  endif
+	}
+# endif
+	if (n == -1 ) {
+		G.flag_repeat = 0;
+		promptme = 0;
+	}
+	n = strlen(console_buffer);
+	console_buffer[n] = '\n';
+	console_buffer[n+1]= '\0';
+	if (had_ctrlc())
+		G.flag_repeat = 0;
+	clear_ctrlc();
+	G.do_repeat = 0;
+#ifndef __U_BOOT__
+	if (G.promptmode == 1) {
+#else /* __U_BOOT__ */
+	if (!G.promptmode) {
+#endif /* __U_BOOT__ */
+		if (console_buffer[0] == '\n'&& G.flag_repeat == 0) {
+			strcpy(the_command, console_buffer);
+		}
+		else {
+			if (console_buffer[0] != '\n') {
+				strcpy(the_command, console_buffer);
+				G.flag_repeat = 1;
+			}
+			else {
+				G.do_repeat = 1;
+			}
+		}
+		i->p = the_command;
+	}
+	else {
+		if (console_buffer[0] != '\n') {
+			if (strlen(the_command) + strlen(console_buffer)
+				< CONFIG_SYS_CBSIZE) {
+				n = strlen(the_command);
+#ifdef __U_BOOT__
+				/*
+				 * To avoid writing to bad places, we check if
+				 * n is greater than 0.
+				 * This bug was found by Harald Seiler.
+				 */
+				if (n > 0)
+					the_command[n-1] = ' ';
+				strcpy(&the_command[n], console_buffer);
+#else /* !__U_BOOT__ */
+			the_command[n-1] = ' ';
+			strcpy(&the_command[n], console_buffer);
+#endif /* !__U_BOOT__ */
+				}
+				else {
+					the_command[0] = '\n';
+					the_command[1] = '\0';
+					G.flag_repeat = 0;
+				}
+		}
+		if (promptme == 0) {
+			the_command[0] = '\n';
+			the_command[1] = '\0';
+		}
+		i->p = console_buffer;
+	}
+#endif /* __U_BOOT__ */
 }
 /* This is the magic location that prints prompts
  * and gets data back from the user */
 static int fgetc_interactive(struct in_str *i)
 {
 	int ch;
+#ifndef __U_BOOT__
 	/* If it's interactive stdin, get new line. */
 	if (G_interactive_fd && i->file == G.HFILE_stdin) {
+#endif /* !__U_BOOT__ */
+#ifndef __U_BOOT__
 		/* Returns first char (or EOF), the rest is in i->p[] */
 		ch = get_user_input(i);
+#else /* __U_BOOT__ */
+		/* Avoid garbage value and make clang happy. */
+		ch = 0;
+		/*
+		 * get_user_input() does not return anything when used in
+		 * U-Boot.
+		 * So, we need to take the read character from i->p[].
+		 */
+		get_user_input(i);
+		if (i->p && *i->p) {
+			ch = *i->p++;
+		}
+#endif /* __U_BOOT__ */
 		G.promptmode = 1; /* PS2 */
 		debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
+#ifndef __U_BOOT__
 	} else {
 		/* Not stdin: script file, sourced file, etc */
 		do ch = hfgetc(i->file); while (ch == '\0');
 	}
+#endif /* !__U_BOOT__ */
 	return ch;
 }
 #else  /* !INTERACTIVE */
+#ifndef __U_BOOT__
 static ALWAYS_INLINE int fgetc_interactive(struct in_str *i)
 {
 	int ch;
 	do ch = hfgetc(i->file); while (ch == '\0');
 	return ch;
 }
+#endif /* !__U_BOOT__ */
 #endif  /* !INTERACTIVE */
 
 static int i_getch(struct in_str *i)
 {
 	int ch;
 
+#ifndef __U_BOOT__
 	if (!i->file) {
 		/* string-based in_str */
 		ch = (unsigned char)*i->p;
@@ -2758,6 +2997,7 @@ static int i_getch(struct in_str *i)
 		return EOF;
 	}
 
+#endif /* !__U_BOOT__ */
 	/* FILE-based in_str */
 
 #if ENABLE_FEATURE_EDITING
@@ -2767,6 +3007,7 @@ static int i_getch(struct in_str *i)
 		goto out;
 	}
 #endif
+#ifndef __U_BOOT__
 	/* peek_buf[] is an int array, not char. Can contain EOF. */
 	ch = i->peek_buf[0];
 	if (ch != 0) {
@@ -2778,6 +3019,7 @@ static int i_getch(struct in_str *i)
 		goto out;
 	}
 
+#endif /* !__U_BOOT__ */
 	ch = fgetc_interactive(i);
  out:
 	debug_printf("file_get: got '%c' %d\n", ch, ch);
@@ -2793,6 +3035,7 @@ static int i_getch(struct in_str *i)
 
 static int i_peek(struct in_str *i)
 {
+#ifndef __U_BOOT__
 	int ch;
 
 	if (!i->file) {
@@ -2827,6 +3070,11 @@ static int i_peek(struct in_str *i)
 	i->peek_buf[0] = ch;
 	/*i->peek_buf[1] = 0; - already is */
 	return ch;
+#else /* __U_BOOT__ */
+	/* string-based in_str */
+	/* Doesn't report EOF on NUL. None of the callers care. */
+	return (unsigned char)*i->p;
+#endif /* __U_BOOT__ */
 }
 
 /* Only ever called if i_peek() was called, and did not return EOF.
@@ -2835,7 +3083,9 @@ static int i_peek(struct in_str *i)
  */
 static int i_peek2(struct in_str *i)
 {
+#ifndef __U_BOOT__
 	int ch;
+#endif /* !__U_BOOT__ */
 
 	/* There are two cases when i->p[] buffer exists.
 	 * (1) it's a string in_str.
@@ -2846,6 +3096,7 @@ static int i_peek2(struct in_str *i)
 	if (i->p)
 		return (unsigned char)i->p[1];
 
+#ifndef __U_BOOT__
 	/* Now we know it is a file-based in_str. */
 
 	/* peek_buf[] is an int array, not char. Can contain EOF. */
@@ -2859,6 +3110,9 @@ static int i_peek2(struct in_str *i)
 
 	debug_printf("file_peek2: got '%c' %d\n", ch, ch);
 	return ch;
+#else
+	return 0;
+#endif /* __U_BOOT__ */
 }
 
 static int i_getch_and_eat_bkslash_nl(struct in_str *input)
@@ -2897,13 +3151,20 @@ static int i_peek_and_eat_bkslash_nl(struct in_str *input)
 	}
 }
 
+#ifndef __U_BOOT__
 static void setup_file_in_str(struct in_str *i, HFILE *fp)
+#else /* __U_BOOT__ */
+static void setup_file_in_str(struct in_str *i)
+#endif /* __U_BOOT__ */
 {
 	memset(i, 0, sizeof(*i));
+#ifndef __U_BOOT__
 	i->file = fp;
 	/* i->p = NULL; */
+#endif /* !__U_BOOT__ */
 }
 
+#ifndef __U_BOOT__
 static void setup_string_in_str(struct in_str *i, const char *s)
 {
 	memset(i, 0, sizeof(*i));
@@ -2911,6 +3172,7 @@ static void setup_string_in_str(struct in_str *i, const char *s)
 	i->p = s;
 }
 
+#endif /* !__U_BOOT__ */
 
 /*
  * o_string support
@@ -2980,10 +3242,12 @@ static void o_addstr(o_string *o, const char *str)
 	o_addblock(o, str, strlen(str));
 }
 
+#ifndef __U_BOOT__
 static void o_addstr_with_NUL(o_string *o, const char *str)
 {
 	o_addblock(o, str, strlen(str) + 1);
 }
+#endif /* !__U_BOOT__ */
 
 #if !BB_MMU
 static void nommu_addchr(o_string *o, int ch)
@@ -2995,6 +3259,7 @@ static void nommu_addchr(o_string *o, int ch)
 # define nommu_addchr(o, str) ((void)0)
 #endif
 
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_MODE_X
 static void x_mode_addchr(int ch)
 {
@@ -3025,6 +3290,7 @@ static void x_mode_flush(void)
 	G.x_mode_buf.length = 0;
 }
 #endif
+#endif /* !__U_BOOT__ */
 
 /*
  * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
@@ -3156,7 +3422,11 @@ static void debug_print_list(const char *prefix, o_string *o, int n)
 	if (n) {
 		const char *p = o->data + (int)(uintptr_t)list[n - 1] + string_start;
 		indent();
+#ifndef __U_BOOT__
 		fdprintf(2, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+#else /* __U_BOOT__ */
+		printf(" total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+#endif /* __U_BOOT__ */
 	}
 }
 #else
@@ -3216,6 +3486,7 @@ static int o_get_last_ptr(o_string *o, int n)
 	return ((int)(uintptr_t)list[n-1]) + string_start;
 }
 
+#ifndef __U_BOOT__
 /*
  * Globbing routines.
  *
@@ -3524,12 +3795,14 @@ static int perform_glob(o_string *o, int n)
 	return n;
 }
 
+#endif /* !__U_BOOT__ */
 #endif /* !HUSH_BRACE_EXPANSION */
 
 /* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered.
  * Otherwise, just finish current list[] and start new */
 static int o_save_ptr(o_string *o, int n)
 {
+#ifndef __U_BOOT__
 	if (o->o_expflags & EXP_FLAG_GLOB) {
 		/* If o->has_empty_slot, list[n] was already globbed
 		 * (if it was requested back then when it was filled)
@@ -3537,6 +3810,7 @@ static int o_save_ptr(o_string *o, int n)
 		if (!o->has_empty_slot)
 			return perform_glob(o, n); /* o_save_ptr_helper is inside */
 	}
+#endif /* !__U_BOOT__ */
 	return o_save_ptr_helper(o, n);
 }
 
@@ -3567,10 +3841,14 @@ static struct pipe *free_pipe(struct pipe *pi)
 	struct pipe *next;
 	int i;
 
+#ifndef __U_BOOT__
 	debug_printf_clean("free_pipe (pid %d)\n", getpid());
+#endif /* !__U_BOOT__ */
 	for (i = 0; i < pi->num_cmds; i++) {
 		struct command *command;
+#ifndef __U_BOOT__
 		struct redir_struct *r, *rnext;
+#endif /* !__U_BOOT__ */
 
 		command = &pi->cmds[i];
 		debug_printf_clean("  command %d:\n", i);
@@ -3605,6 +3883,7 @@ static struct pipe *free_pipe(struct pipe *pi)
 		free(command->group_as_string);
 		//command->group_as_string = NULL;
 #endif
+#ifndef __U_BOOT__
 		for (r = command->redirects; r; r = rnext) {
 			debug_printf_clean("   redirect %d%s",
 					r->rd_fd, redir_table[r->rd_type].descrip);
@@ -3619,13 +3898,16 @@ static struct pipe *free_pipe(struct pipe *pi)
 			free(r);
 		}
 		//command->redirects = NULL;
+#endif /* !__U_BOOT__ */
 	}
 	free(pi->cmds);   /* children are an array, they get freed all at once */
 	//pi->cmds = NULL;
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_JOB
 	free(pi->cmdtext);
 	//pi->cmdtext = NULL;
 #endif
+#endif /* !__U_BOOT__ */
 
 	next = pi->next;
 	free(pi);
@@ -3655,6 +3937,7 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 		[PIPE_OR ] = "OR" ,
 		[PIPE_BG ] = "BG" ,
 	};
+#ifndef __U_BOOT__
 	static const char *RES[] = {
 		[RES_NONE ] = "NONE" ,
 # if ENABLE_HUSH_IF
@@ -3684,6 +3967,7 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 		[RES_XXXX ] = "XXXX" ,
 		[RES_SNTX ] = "SNTX" ,
 	};
+#endif /* !__U_BOOT__ */
 	static const char *const CMDTYPE[] = {
 		"{}",
 		"()",
@@ -3701,8 +3985,10 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 				lvl*2, "",
 				pin,
 				pi->num_cmds,
+#ifdef __U_BOOT__
 				(IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
 				RES[pi->res_word],
+#endif /* !__U_BOOT__ */
 				pi->followup, PIPE[pi->followup]
 		);
 		prn = 0;
@@ -3734,8 +4020,10 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 				fdprintf(2, " '%s'", *argv);
 				argv++;
 			}
+#ifndef __U_BOOT__
 			if (command->redirects)
 				fdprintf(2, " {redir}");
+#endif /* __U_BOOT__ */
 			fdprintf(2, "\n");
 			prn++;
 		}
@@ -3869,8 +4157,13 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
 	) {
 		struct pipe *new_p;
 		debug_printf_parse("done_pipe: adding new pipe: "
+#ifndef __U_BOOT__
 				"not_null:%d ctx->ctx_res_w:%d\n",
 				not_null, ctx->ctx_res_w);
+#else /* __U_BOOT__ */
+				"not_null:%d\n",
+				not_null);
+#endif /* __U_BOOT__ */
 		new_p = new_pipe();
 		ctx->pipe->next = new_p;
 		ctx->pipe = new_p;
@@ -4096,6 +4389,7 @@ static int done_word(struct parse_context *ctx)
 		return 0;
 	}
 
+#ifndef __U_BOOT__
 	if (ctx->pending_redirect) {
 		/* We do not glob in e.g. >*.tmp case. bash seems to glob here
 		 * only if run as "bash", not "sh" */
@@ -4137,6 +4431,7 @@ static int done_word(struct parse_context *ctx)
 		debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data);
 		ctx->pending_redirect = NULL;
 	} else {
+#endif /* !__U_BOOT__ */
 #if HAS_KEYWORDS
 # if ENABLE_HUSH_CASE
 		if (ctx->ctx_dsemicolon
@@ -4247,8 +4542,14 @@ static int done_word(struct parse_context *ctx)
 		}
 		debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]);
 		command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data));
+#ifdef __U_BOOT__
+		command->argc++;
+#endif /* __U_BOOT__ */
 		debug_print_strings("word appended to argv", command->argv);
+
+#ifndef __U_BOOT__
 	}
+#endif /* !__U_BOOT__ */
 
 #if ENABLE_HUSH_LOOPS
 	if (ctx->ctx_res_w == RES_FOR) {
@@ -4280,6 +4581,7 @@ static int done_word(struct parse_context *ctx)
 }
 
 
+#ifndef __U_BOOT__
 /* Peek ahead in the input to find out if we have a "&n" construct,
  * as in "2>&1", that represents duplicating a file descriptor.
  * Return:
@@ -4463,7 +4765,11 @@ static char *fetch_till_str(o_string *as_string,
 			nommu_addchr(as_string, ch);
 		if (ch == '\n' || ch == EOF) {
  check_heredoc_end:
+#ifndef __U_BOOT__
 			if ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\') {
+#else /* __U_BOOT__ */
+			if (prev != '\\') {
+#endif
 				/* End-of-line, and not a line continuation */
 				if (strcmp(heredoc.data + past_EOL, word) == 0) {
 					heredoc.data[past_EOL] = '\0';
@@ -4483,7 +4789,11 @@ static char *fetch_till_str(o_string *as_string,
 						ch = i_getch(input);
 						if (ch != EOF)
 							nommu_addchr(as_string, ch);
+#ifndef __U_BOOT__
 					} while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t');
+#else /* __U_BOOT__ */
+				} while (ch == '\t');
+#endif
 					/* If this immediately ended the line,
 					 * go back to end-of-line checks.
 					 */
@@ -4520,6 +4830,7 @@ static char *fetch_till_str(o_string *as_string,
 			prev = ch;
 	}
 }
+#endif /* !__U_BOOT__ */
 
 /* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs
  * and load them all. There should be exactly heredoc_cnt of them.
@@ -4539,10 +4850,13 @@ static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt,
 				cmd->argv ? cmd->argv[0] : "NONE"
 		);
 		for (i = 0; i < pi->num_cmds; i++) {
+#ifndef __U_BOOT__
 			struct redir_struct *redir = cmd->redirects;
 
+#endif /* !__U_BOOT__ */
 			debug_printf_heredoc("fetch_heredocs: %d cmd argv0:'%s'\n",
 					i, cmd->argv ? cmd->argv[0] : "NONE");
+#ifndef __U_BOOT__
 			while (redir) {
 				if (redir->rd_type == REDIRECT_HEREDOC) {
 					char *p;
@@ -4561,6 +4875,7 @@ static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt,
 				}
 				redir = redir->next;
 			}
+#endif /* !__U_BOOT__ */
 			if (cmd->group) {
 				//bb_error_msg("%s:%u heredoc_cnt:%d", __func__, __LINE__, heredoc_cnt);
 				heredoc_cnt = fetch_heredocs(as_string, cmd->group, heredoc_cnt, input);
@@ -4651,13 +4966,17 @@ static int parse_group(struct parse_context *ctx,
 	}
 #endif
 
+#ifndef __U_BOOT__
  IF_HUSH_FUNCTIONS(skip:)
+#endif /* !__U_BOOT__ */
 
 	endch = '}';
 	if (ch == '(') {
 		endch = ')';
+#ifndef __U_BOOT__
 		IF_HUSH_FUNCTIONS(if (command->cmd_type != CMD_FUNCDEF))
 			command->cmd_type = CMD_SUBSHELL;
+#endif /* !__U_BOOT__ */
 	} else {
 		/* bash does not allow "{echo...", requires whitespace */
 		ch = i_peek(input);
@@ -4725,6 +5044,7 @@ static int parse_group(struct parse_context *ctx,
 #undef as_string
 }
 
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS
 /* Subroutines for copying $(...) and `...` things */
 /* '...' */
@@ -4754,6 +5074,7 @@ static int add_till_single_quote_dquoted(o_string *dest, struct in_str *input)
 		o_addqchr(dest, ch);
 	}
 }
+
 /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
 static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
 static int add_till_double_quote(o_string *dest, struct in_str *input)
@@ -4780,6 +5101,8 @@ static int add_till_double_quote(o_string *dest, struct in_str *input)
 		//if (ch == '$') ...
 	}
 }
+
+
 /* Process `cmd` - copy contents until "`" is seen. Complicated by
  * \` quoting.
  * "Within the backquoted style of command substitution, backslash
@@ -5014,6 +5337,7 @@ static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_st
 #else
 # #define parse_dollar_squote(as_string, dest, input) 0
 #endif /* BASH_DOLLAR_SQUOTE */
+#endif /* !__U_BOOT__ */
 
 /* Return code: 0 for OK, 1 for syntax error */
 #if BB_MMU
@@ -5056,8 +5380,10 @@ static int parse_dollar(o_string *as_string,
 		o_addchr(dest, ch | quote_mask);
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 	} else switch (ch) {
+#ifndef __U_BOOT__
 	case '$': /* pid */
 	case '!': /* last bg pid */
+#endif /* !__U_BOOT__ */
 	case '?': /* last exit code */
 	case '#': /* number of args */
 	case '*': /* args */
@@ -5132,7 +5458,9 @@ static int parse_dollar(o_string *as_string,
 				break;
 			if (!isalnum(ch) && ch != '_') {
 				unsigned end_ch;
+#ifndef __U_BOOT__
 				unsigned char last_ch;
+#endif /* !__U_BOOT__ */
 				/* handle parameter expansions
 				 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
 				 */
@@ -5151,6 +5479,7 @@ static int parse_dollar(o_string *as_string,
  eat_until_closing:
 				/* Eat everything until closing '}' (or ':') */
 				end_ch = '}';
+#ifndef __U_BOOT__
 				if (BASH_SUBSTR
 				 && ch == ':'
 				 && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input))
@@ -5169,6 +5498,7 @@ static int parse_dollar(o_string *as_string,
 					}
 					end_ch = '}' * 0x100 + '/';
 				}
+#endif /* !__U_BOOT__ */
 				o_addchr(dest, ch);
 				/* The pattern can't be empty.
 				 * IOW: if the first char after "${v//" is a slash,
@@ -5179,21 +5509,28 @@ static int parse_dollar(o_string *as_string,
 				if (i_peek(input) == '/') {
 					o_addchr(dest, i_getch(input));
 				}
+#ifndef __U_BOOT__
  again:
+#endif /* !__U_BOOT__ */
 				if (!BB_MMU)
 					pos = dest->length;
 #if ENABLE_HUSH_DOLLAR_OPS
+#ifndef __U_BOOT__
 				last_ch = add_till_closing_bracket(dest, input, end_ch);
 				if (last_ch == 0) /* error? */
 					return 0;
+#endif /* !__U_BOOT__ */
 #else
 # error Simple code to only allow ${var} is not implemented
 #endif
 				if (as_string) {
 					o_addstr(as_string, dest->data + pos);
+#ifndef __U_BOOT__
 					o_addchr(as_string, last_ch);
+#endif /* !__U_BOOT__ */
 				}
 
+#ifndef __U_BOOT__
 				if ((BASH_SUBSTR || BASH_PATTERN_SUBST)
 					 && (end_ch & 0xff00)
 				) {
@@ -5212,6 +5549,7 @@ static int parse_dollar(o_string *as_string,
 						o_addstr(dest, "999999999");
 					} /* else: it's ${var/[/]pattern} */
 				}
+#endif /* !__U_BOOT__ */
 				break;
 			}
 			len_single_ch = 0; /* it can't be ${#C} op */
@@ -5415,8 +5753,10 @@ static struct pipe *parse_stream(char **pstring,
 		const char *is_special;
 		int ch;
 		int next;
+#ifndef __U_BOOT__
 		int redir_fd;
 		redir_type redir_style;
+#endif /* !__U_BOOT__ */
 
 		ch = i_getch(input);
 		debug_printf_parse(": ch=%c (%d) escape=%d\n",
@@ -5506,8 +5846,10 @@ static struct pipe *parse_stream(char **pstring,
 		if (ch == '\'') {
 			ctx.word.has_quoted_part = 1;
 			next = i_getch(input);
+#ifndef __U_BOOT__
 			if (next == '\'' && !ctx.pending_redirect)
 				goto insert_empty_quoted_str_marker;
+#endif /* !__U_BOOT__ */
 
 			ch = next;
 			while (1) {
@@ -5534,7 +5876,11 @@ static struct pipe *parse_stream(char **pstring,
 			next = i_peek_and_eat_bkslash_nl(input);
 
 		is_special = "{}<>&|();#" /* special outside of "str" */
+#ifndef __U_BOOT__
 				"$\"" IF_HUSH_TICK("`") /* always special */
+#else /* __U_BOOT__ */
+				"$\""
+#endif /* __U_BOOT__ */
 				SPECIAL_VAR_SYMBOL_STR;
 #if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
 		if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) {
@@ -5649,7 +5995,11 @@ static struct pipe *parse_stream(char **pstring,
 		 * } is an ordinary char in this case, even inside { cmd; }
 		 * Pathological example: { ""}; } should exec "}" cmd
 		 */
+#ifndef __U_BOOT__
 		if (ch == '}') {
+#else /* __U_BOOT__ */
+		if (ch == '}' || ch == ')') {
+#endif /* __U_BOOT__ */
 			if (ctx.word.length != 0 /* word} */
 			 || ctx.word.has_quoted_part    /* ""} */
 			) {
@@ -5725,6 +6075,7 @@ static struct pipe *parse_stream(char **pstring,
 		/* Catch <, > before deciding whether this word is
 		 * an assignment. a=1 2>z b=2: b=2 is still assignment */
 		switch (ch) {
+#ifndef __U_BOOT__
 		case '>':
 			redir_fd = redirect_opt_num(&ctx.word);
 			if (done_word(&ctx)) {
@@ -5771,6 +6122,7 @@ static struct pipe *parse_stream(char **pstring,
 			if (parse_redirect(&ctx, redir_fd, redir_style, input))
 				goto parse_error_exitcode1;
 			continue; /* get next char */
+#endif /* !__U_BOOT__ */
 		case '#':
 			if (ctx.word.length == 0 && !ctx.word.has_quoted_part) {
 				/* skip "#comment" */
@@ -5798,8 +6150,10 @@ static struct pipe *parse_stream(char **pstring,
  skip_end_trigger:
 
 		if (ctx.is_assignment == MAYBE_ASSIGNMENT
+#ifndef __U_BOOT__
 		 /* check that we are not in word in "a=1 2>word b=1": */
 		 && !ctx.pending_redirect
+#endif /* !__U_BOOT__ */
 		) {
 			/* ch is a special char and thus this word
 			 * cannot be an assignment */
@@ -5821,8 +6175,10 @@ static struct pipe *parse_stream(char **pstring,
 			o_addchr(&ctx.word, ch);
 			continue; /* get next char */
 		case '$':
+#ifndef __U_BOOT__
 			if (parse_dollar_squote(&ctx.as_string, &ctx.word, input))
 				continue; /* get next char */
+#endif /* !__U_BOOT__ */
 			if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) {
 				debug_printf_parse("parse_stream parse error: "
 					"parse_dollar returned 0 (error)\n");
@@ -5831,9 +6187,15 @@ static struct pipe *parse_stream(char **pstring,
 			continue; /* get next char */
 		case '"':
 			ctx.word.has_quoted_part = 1;
+#ifndef __U_BOOT__
 			if (next == '"' && !ctx.pending_redirect) {
+#else /* __U_BOOT__ */
+			if (next == '"') {
+#endif /* __U_BOOT__ */
 				i_getch(input); /* eat second " */
+#ifndef __U_BOOT__
  insert_empty_quoted_str_marker:
+#endif /* !__U_BOOT__ */
 				nommu_addchr(&ctx.as_string, next);
 				o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
 				o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
@@ -6140,6 +6502,7 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
 	return n;
 }
 
+#ifndef __U_BOOT__
 /* Helper to expand $((...)) and heredoc body. These act as if
  * they are in double quotes, with the exception that they are not :).
  * Just the rules are similar: "expand only $var and `cmd`"
@@ -6440,7 +6803,9 @@ static int encode_then_append_var_plusminus(o_string *output, int n,
 	o_free(&dest);
 	return n;
 }
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 #if ENABLE_FEATURE_SH_MATH
 static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 {
@@ -6461,7 +6826,9 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 	return res;
 }
 #endif
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 #if BASH_PATTERN_SUBST
 /* ${var/[/]pattern[/repl]} helpers */
 static char *strstr_pattern(char *val, const char *pattern, int *size)
@@ -6529,6 +6896,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
 	return result;
 }
 #endif /* BASH_PATTERN_SUBST */
+#endif /* !__U_BOOT__ */
 
 static int append_str_maybe_ifs_split(o_string *output, int n,
 		int first_ch, const char *val)
@@ -6593,6 +6961,7 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 			exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
 		}
 		exp_op = exp_save = *exp_saveptr;
+#ifndef __U_BOOT__
 		if (exp_op) {
 			exp_word = exp_saveptr + 1;
 			if (exp_op == ':') {
@@ -6608,29 +6977,37 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 			}
 			*exp_saveptr = '\0';
 		} /* else: it's not an expansion op, but bare ${var} */
+#endif /* !__U_BOOT__ */
 	}
 
 	/* Look up the variable in question */
 	if (isdigit(var[0])) {
 		/* parse_dollar should have vetted var for us */
+#ifndef __U_BOOT__
 		int nn = xatoi_positive(var);
+#else /* __U_BOOT__ */
+		int nn = simple_strtoul(var, NULL, 10);
+#endif /* __U_BOOT__ */
 		if (nn < G.global_argc)
 			val = G.global_argv[nn];
 		/* else val remains NULL: $N with too big N */
 	} else {
 		switch (var[0]) {
+#ifndef __U_BOOT__
 		case '$': /* pid */
 			val = utoa(G.root_pid);
 			break;
 		case '!': /* bg pid */
 			val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
 			break;
+#endif /* !__U_BOOT__ */
 		case '?': /* exitcode */
 			val = utoa(G.last_exitcode);
 			break;
 		case '#': /* argc */
 			val = utoa(G.global_argc ? G.global_argc-1 : 0);
 			break;
+#ifndef __U_BOOT__
 		case '-': { /* active options */
 			/* Check set_mode() to see what option chars we support */
 			char *cp;
@@ -6652,11 +7029,13 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 			*cp = '\0';
 			break;
 		}
+#endif /* !__U_BOOT__ */
 		default:
 			val = get_local_var_value(var);
 		}
 	}
 
+#ifndef __U_BOOT__
 	/* Handle any expansions */
 	if (exp_op == 'L') {
 		reinit_unicode_for_hush();
@@ -6936,6 +7315,7 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 		*exp_saveptr = exp_save;
 	} /* if (exp_op) */
 
+#endif /* !__U_BOOT__ */
 	arg[0] = arg0;
 	*pp = p;
 
@@ -7248,6 +7628,7 @@ static char* expand_strvec_to_string(char **argv)
 }
 #endif
 
+#ifndef __U_BOOT__
 static char **expand_assignments(char **argv, int count)
 {
 	int i;
@@ -7289,7 +7670,9 @@ static void switch_off_special_sigs(unsigned mask)
 		install_sighandler(sig, SIG_DFL);
 	}
 }
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 #if BB_MMU
 /* never called */
 void re_execute_shell(char ***to_free, const char *s,
@@ -7479,6 +7862,7 @@ static void re_execute_shell(char ***to_free, const char *s,
 }
 #endif  /* !BB_MMU */
 
+#endif /* !__U_BOOT__ */
 
 static int run_and_free_list(struct pipe *pi);
 
@@ -7537,6 +7921,7 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
 	}
 }
 
+#ifndef __U_BOOT__
 static void parse_and_run_string(const char *s)
 {
 	struct in_str input;
@@ -7546,18 +7931,30 @@ static void parse_and_run_string(const char *s)
 	parse_and_run_stream(&input, '\0');
 	//IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
 }
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 static void parse_and_run_file(HFILE *fp)
+#else /* __U_BOOT__ */
+void parse_and_run_file(void)
+#endif /* __U_BOOT__ */
 {
 	struct in_str input;
+#ifndef __U_BOOT__
 	IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
 
 	IF_HUSH_LINENO_VAR(G.parse_lineno = 1;)
 	setup_file_in_str(&input, fp);
+#else /* __U_BOOT__ */
+	setup_file_in_str(&input);
+#endif /* __U_BOOT__ */
 	parse_and_run_stream(&input, ';');
+#ifndef __U_BOOT__
 	IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
+#endif /* !__U_BOOT__ */
 }
 
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_TICK
 static int generate_stream_from_string(const char *s, pid_t *pid_p)
 {
@@ -8163,7 +8560,9 @@ static const char * FAST_FUNC get_builtin_name(int i)
 	return NULL;
 }
 #endif
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 static void remove_nested_vars(void)
 {
 	struct variable *cur;
@@ -8215,6 +8614,7 @@ static void leave_var_nest_level(void)
 
 	remove_nested_vars();
 }
+#endif /* __U_BOOT__ */
 
 #if ENABLE_HUSH_FUNCTIONS
 static struct function **find_function_slot(const char *name)
@@ -8399,6 +8799,7 @@ static int run_function(const struct function *funcp, char **argv)
 #endif /* ENABLE_HUSH_FUNCTIONS */
 
 
+#ifndef __U_BOOT__
 #if BB_MMU
 #define exec_builtin(to_free, x, argv) \
 	exec_builtin(x, argv)
@@ -8431,8 +8832,10 @@ static void exec_builtin(char ***to_free,
 			argv);
 #endif
 }
+#endif /* !__U_BOOT__ */
 
 
+#ifndef __U_BOOT__
 static void execvp_or_die(char **argv) NORETURN;
 static void execvp_or_die(char **argv)
 {
@@ -8514,7 +8917,9 @@ static void dump_cmd_in_x_mode(char **argv)
 #else
 # define dump_cmd_in_x_mode(argv) ((void)0)
 #endif
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_COMMAND
 static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *explanation)
 {
@@ -8540,6 +8945,7 @@ static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *exp
 #else
 # define if_command_vV_print_and_exit(a,b,c) ((void)0)
 #endif
+#endif /* !__U_BOOT__ */
 
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
@@ -8548,6 +8954,7 @@ static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *exp
 	pseudo_exec(command, argv_expanded)
 #endif
 
+#ifndef __U_BOOT__
 /* Called after [v]fork() in run_pipe, or from builtin_exec.
  * Never returns.
  * Don't exit() here.  If you don't exec, use _exit instead.
@@ -9186,18 +9593,34 @@ static int redirect_and_varexp_helper(
 
 	return setup_redirects(command, sqp);
 }
+#endif /* !__U_BOOT__ */
+
 static NOINLINE int run_pipe(struct pipe *pi)
 {
 	static const char *const null_ptr = NULL;
 
 	int cmd_no;
+#ifndef __U_BOOT__
 	int next_infd;
+#endif /* !__U_BOOT__ */
 	struct command *command;
 	char **argv_expanded;
 	char **argv;
+#ifndef __U_BOOT__
 	struct squirrel *squirrel = NULL;
+#endif /* !__U_BOOT__ */
 	int rcode;
 
+#ifdef __U_BOOT__
+	/*
+	 * Set rcode here to avoid returning a garbage value in the middle of
+	 * the function.
+	 * Also, if an error occurs, rcode value would be changed and last
+	 * return will signal the error.
+	 */
+	rcode = 0;
+#endif /* __U_BOOT__ */
+
 	debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
 	debug_enter();
 
@@ -9230,11 +9653,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		G.ifs_whitespace = (char*)G.ifs;
 	}
 
+#ifndef __U_BOOT__
 	IF_HUSH_JOB(pi->pgrp = -1;)
 	pi->stopped_cmds = 0;
+#endif /* !__U_BOOT__ */
 	command = &pi->cmds[0];
 	argv_expanded = NULL;
 
+#ifndef __U_BOOT__
 	if (pi->num_cmds != 1
 	 || pi->followup == PIPE_BG
 	 || command->cmd_type == CMD_SUBSHELL
@@ -9243,6 +9669,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 	}
 
 	pi->alive_cmds = 1;
+#endif /* !__U_BOOT__ */
 
 	debug_printf_exec(": group:%p argv:'%s'\n",
 		command->group, command->argv ? command->argv[0] : "NONE");
@@ -9274,6 +9701,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		/* { list } */
 		debug_printf_exec("non-subshell group\n");
 		rcode = 1; /* exitcode if redir failed */
+#ifndef __U_BOOT__
 		if (setup_redirects(command, &squirrel) == 0) {
 			debug_printf_exec(": run_list\n");
 //FIXME: we need to pass squirrel down into run_list()
@@ -9286,6 +9714,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		}
 		restore_redirects(squirrel);
 		IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+#endif /* !__U_BOOT__ */
 		debug_leave();
 		debug_printf_exec("run_pipe: return %d\n", rcode);
 		return rcode;
@@ -9293,10 +9722,12 @@ static NOINLINE int run_pipe(struct pipe *pi)
 
 	argv = command->argv ? command->argv : (char **) &null_ptr;
 	{
+#ifndef __U_BOOT__
 		const struct built_in_command *x;
 		IF_HUSH_FUNCTIONS(const struct function *funcp;)
 		IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };)
 		struct variable **sv_shadowed;
+#endif /* !__U_BOOT__ */
 		struct variable *old_vars;
 
 #if ENABLE_HUSH_LINENO_VAR
@@ -9311,8 +9742,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			unsigned i;
 			G.expand_exitcode = 0;
  only_assignments:
+#ifndef __U_BOOT__
 			rcode = setup_redirects(command, &squirrel);
 			restore_redirects(squirrel);
+#endif /* !__U_BOOT__ */
 
 			/* Set shell variables */
 			i = 0;
@@ -9335,7 +9768,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
 				}
 #endif
 				debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
+#ifndef __U_BOOT__
 				if (set_local_var(p, /*flag:*/ 0)) {
+#else /* __U_BOOT__ */
+				if (set_local_var_2021(p, /*flag:*/ 0)) {
+#endif
 					/* assignment to readonly var / putenv error? */
 					rcode = 1;
 				}
@@ -9378,6 +9815,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		}
 
 		old_vars = NULL;
+#ifndef __U_BOOT__
 		sv_shadowed = G.shadowed_vars_pp;
 
 		/* Check if argv[0] matches any functions (this goes before bltins) */
@@ -9501,8 +9939,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		debug_leave();
 		debug_printf_exec("run_pipe return %d\n", rcode);
 		return rcode;
+#endif /* !__U_BOOT__ */
 	}
 
+#ifndef __U_BOOT__
  must_fork:
 	/* NB: argv_expanded may already be created, and that
 	 * might include `cmd` runs! Do not rerun it! We *must*
@@ -9511,9 +9951,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
 	/* Going to fork a child per each pipe member */
 	pi->alive_cmds = 0;
 	next_infd = 0;
+#endif /* !__U_BOOT__ */
 
 	cmd_no = 0;
 	while (cmd_no < pi->num_cmds) {
+#ifndef __U_BOOT__
 		struct fd_pair pipefds;
 #if !BB_MMU
 		int sv_var_nest_level = G.var_nest_level;
@@ -9522,6 +9964,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		nommu_save.argv = NULL;
 		nommu_save.argv_from_re_execing = NULL;
 #endif
+#endif /* !__U_BOOT__ */
 		command = &pi->cmds[cmd_no];
 		cmd_no++;
 		if (command->argv) {
@@ -9531,6 +9974,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			debug_printf_exec(": pipe member with no argv\n");
 		}
 
+#ifndef __U_BOOT__
 		/* pipes are inserted between pairs of commands */
 		pipefds.rd = 0;
 		pipefds.wr = 1;
@@ -9633,17 +10077,30 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			close(pipefds.wr);
 		/* Pass read (output) pipe end to next iteration */
 		next_infd = pipefds.rd;
+#else /* __U_BOOT__ */
+		/* Process the command */
+		rcode = cmd_process(G.do_repeat ? CMD_FLAG_REPEAT : 0,
+				    command->argc, command->argv,
+				    &(G.flag_repeat), NULL);
+#endif /* __U_BOOT__ */
 	}
 
+#ifndef __U_BOOT__
 	if (!pi->alive_cmds) {
 		debug_leave();
 		debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
 		return 1;
 	}
+#endif /* __U_BOOT__ */
 
 	debug_leave();
+#ifndef __U_BOOT__
 	debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds);
 	return -1;
+#else /* __U_BOOT__ */
+	debug_printf_exec("run_pipe return %d\n", rcode);
+	return rcode;
+#endif /* __U_BOOT__ */
 }
 
 /* NB: called by pseudo_exec, and therefore must not modify any
@@ -9670,8 +10127,10 @@ static int run_list(struct pipe *pi)
 	smallint last_rword; /* ditto */
 #endif
 
+#ifndef __U_BOOT__
 	debug_printf_exec("run_list start lvl %d\n", G.run_list_level);
 	debug_enter();
+#endif /* !__U_BOOT__ */
 
 #if ENABLE_HUSH_LOOPS
 	/* Check syntax for "for" */
@@ -9719,10 +10178,15 @@ static int run_list(struct pipe *pi)
 	rcode = G.last_exitcode;
 
 	/* Go through list of pipes, (maybe) executing them. */
+#ifndef __U_BOOT__
 	for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
+#else /* __U_BOOT__ */
+	for (; pi; pi = pi->next) {
+#endif /* __U_BOOT__ */
 		int r;
 		int sv_errexit_depth;
 
+#ifndef __U_BOOT__
 		if (G.flag_SIGINT)
 			break;
 		if (G_flag_return_in_progress == 1)
@@ -9732,6 +10196,7 @@ static int run_list(struct pipe *pi)
 		debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
 				rword, cond_code, last_rword);
 
+#endif /* !__U_BOOT__ */
 		sv_errexit_depth = G.errexit_depth;
 		if (
 #if ENABLE_HUSH_IF
@@ -9885,8 +10350,10 @@ static int run_list(struct pipe *pi)
 		 * OTOH, in non-interactive shell this is useless
 		 * and only leads to extra job checks */
 		if (pi->num_cmds == 0) {
+#ifndef __U_BOOT__
 			if (G_interactive_fd)
 				goto check_jobs_and_continue;
+#endif /* !__U_BOOT__ */
 			continue;
 		}
 
@@ -9895,20 +10362,33 @@ static int run_list(struct pipe *pi)
 		 * after run_pipe to collect any background children,
 		 * even if list execution is to be stopped. */
 		debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_LOOPS
 		G.flag_break_continue = 0;
 #endif
+#endif /* !__U_BOOT__ */
 		rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
+#ifdef __U_BOOT__
+		if (r == -2) {
+			/* -2 indicates exit was called, so we need to quit now. */
+			G.last_exitcode = rcode;
+
+			break;
+		}
+#endif
 		if (r != -1) {
 			/* We ran a builtin, function, or group.
 			 * rcode is already known
 			 * and we don't need to wait for anything. */
 			debug_printf_exec(": builtin/func exitcode %d\n", rcode);
 			G.last_exitcode = rcode;
+#ifndef __U_BOOT__
 			check_and_run_traps();
+#endif /* !__U_BOOT__ */
 #if ENABLE_HUSH_TRAP && ENABLE_HUSH_FUNCTIONS
 			rcode = G.last_exitcode; /* "return" in trap can change it, read back */
 #endif
+#ifndef __U_BOOT__
 #if ENABLE_HUSH_LOOPS
 			/* Was it "break" or "continue"? */
 			if (G.flag_break_continue) {
@@ -9934,6 +10414,7 @@ static int run_list(struct pipe *pi)
 				checkjobs(NULL, 0 /*(no pid to wait for)*/);
 				break;
 			}
+
 		} else if (pi->followup == PIPE_BG) {
 			/* What does bash do with attempts to background builtins? */
 			/* even bash 3.2 doesn't do that well with nested bg:
@@ -9969,13 +10450,18 @@ static int run_list(struct pipe *pi)
 			rcode = G.last_exitcode; /* "return" in trap can change it, read back */
 #endif
 		}
+#endif /* !__U_BOOT__ */
 
+#ifndef __U_BOOT__
 		/* 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);
 		}
+#else /* __U_BOOT__ */
+		} /* if (r != -1) */
+#endif /* __U_BOOT__ */
 		G.errexit_depth = sv_errexit_depth;
 
 		/* Analyze how result affects subsequent commands */
@@ -9983,8 +10469,10 @@ static int run_list(struct pipe *pi)
 		if (rword == RES_IF || rword == RES_ELIF)
 			cond_code = rcode;
 #endif
+#ifndef __U_BOOT__
  check_jobs_and_continue:
 		checkjobs(NULL, 0 /*(no pid to wait for)*/);
+#endif /* !__U_BOOT__ */
  dont_check_jobs_but_continue: ;
 #if ENABLE_HUSH_LOOPS
 		/* Beware of "while false; true; do ..."! */
@@ -10021,8 +10509,10 @@ static int run_list(struct pipe *pi)
 #if ENABLE_HUSH_CASE
 	free(case_word);
 #endif
+#ifndef __U_BOOT__
 	debug_leave();
 	debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
+#endif /* !__U_BOOT__ */
 	return rcode;
 }
 
@@ -10031,10 +10521,14 @@ static int run_and_free_list(struct pipe *pi)
 {
 	int rcode = 0;
 	debug_printf_exec("run_and_free_list entered\n");
+#ifndef __U_BOOT__
 	if (!G.o_opt[OPT_O_NOEXEC]) {
+#endif /* !__U_BOOT__ */
 		debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
 		rcode = run_list(pi);
+#ifndef __U_BOOT__
 	}
+#endif /* !__U_BOOT__ */
 	/* free_pipe_list has the side effect of clearing memory.
 	 * In the long run that function can be merged with run_list,
 	 * but doing that now would hobble the debugging effort. */
@@ -10044,6 +10538,7 @@ static int run_and_free_list(struct pipe *pi)
 }
 
 
+#ifndef __U_BOOT__
 static void install_sighandlers(unsigned mask)
 {
 	sighandler_t old_handler;
@@ -10460,6 +10955,7 @@ int hush_main(int argc, char **argv)
 		}
 	}
 
+#ifndef __U_BOOT__
 	/* -c takes effect *after* -l */
 	if (G.opt_c) {
 		/* Possibilities:
@@ -10536,6 +11032,7 @@ int hush_main(int argc, char **argv)
 	/* "implicit" -s: bare interactive hush shows 's' in $- */
 	G.opt_s = 1;
 
+#endif /* __U_BOOT__ */
 	/* Up to here, shell was non-interactive. Now it may become one.
 	 * NB: don't forget to (re)run install_special_sighandlers() as needed.
 	 */
@@ -10681,6 +11178,7 @@ int hush_main(int argc, char **argv)
 }
 
 
+
 /*
  * Built-ins
  */
@@ -12160,3 +12658,4 @@ static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
 	return l;
 }
 #endif
+#endif /* !__U_BOOT__ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 09/23] cli: Add menu for hush parser
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (7 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 08/23] cli: Port Busybox 2021 hush to U-Boot Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:46 ` [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag Francis Laniel
                   ` (13 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios, Marek Vasut,
	Rasmus Villemoes, AKASHI Takahiro, Rui Miguel Silva,
	Linus Walleij, Andrew Scull

For the moment, the menu contains only entry: HUSH_OLD_PARSER which is the
default.
The goal is to prepare the field to add a new hush parser which guarantees
actual behavior is still correct.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 cmd/Kconfig     | 21 +++++++++++++++++++++
 common/Makefile |  3 ++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 2caa4af71c..1b9d04680d 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -23,6 +23,27 @@ config HUSH_PARSER
 	  If disabled, you get the old, much simpler behaviour with a somewhat
 	  smaller memory footprint.
 
+menu "Hush flavor to use"
+	depends on HUSH_PARSER
+
+	config HUSH_OLD_PARSER
+		bool "Use hush old parser"
+		default y
+		help
+		  This option enables the old flavor of hush based on hush Busybox from
+		  2005.
+
+		  It is actually the default U-Boot shell when decided to use hush as shell.
+
+	config HUSH_2021_PARSER
+		bool "Use hush 2021 parser"
+		help
+		  This option enables the new flavor of hush based on hush Busybox from
+		  2021.
+
+		  For the moment, it is highly experimental and should be used at own risks.
+endmenu
+
 config CMDLINE_EDITING
 	bool "Enable command line editing"
 	depends on CMDLINE
diff --git a/common/Makefile b/common/Makefile
index a50302d8b5..ddb89306ee 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -8,7 +8,8 @@ ifndef CONFIG_SPL_BUILD
 obj-y += init/
 obj-y += main.o
 obj-y += exports.o
-obj-$(CONFIG_HUSH_PARSER) += cli_hush.o
+obj-$(CONFIG_HUSH_OLD_PARSER) += cli_hush.o
+obj-$(CONFIG_HUSH_2021_PARSER) += cli_hush_2021.o
 obj-$(CONFIG_AUTOBOOT) += autoboot.o
 
 # # boards
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (8 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 09/23] cli: Add menu for hush parser Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:46 ` [RFC PATCH v7 11/23] cmd: Add new parser command Francis Laniel
                   ` (12 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Samuel Dionne-Riel,
	Alexey Romanov, Marek Vasut, Fabrice Gasnier, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

This flag is used to indicate we are using the hush parser.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 common/cli.c                      | 2 ++
 include/asm-generic/global_data.h | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/common/cli.c b/common/cli.c
index 9451e6a142..5eef0cad8c 100644
--- a/common/cli.c
+++ b/common/cli.c
@@ -255,6 +255,8 @@ void cli_loop(void)
 void cli_init(void)
 {
 #ifdef CONFIG_HUSH_PARSER
+	if (!(gd->flags & GD_FLG_HUSH_OLD_PARSER))
+		gd->flags |= GD_FLG_HUSH_OLD_PARSER;
 	u_boot_hush_start();
 #endif
 
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index da17ac8cbc..d61aa4346d 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -650,6 +650,10 @@ enum gd_flags {
 	 * @GD_FLG_FDT_CHANGED: Device tree change has been detected by tests
 	 */
 	GD_FLG_FDT_CHANGED = 0x100000,
+	/**
+	 * @GD_FLG_HUSH_OLD_PARSER: Use hush old parser.
+	 */
+	GD_FLG_HUSH_OLD_PARSER = 0x200000,
 };
 
 #endif /* __ASSEMBLY__ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 11/23] cmd: Add new parser command
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (9 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag Francis Laniel
@ 2023-03-30 19:46 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser Francis Laniel
                   ` (11 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:46 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Robert Marko, Alexey Romanov,
	Fabrice Gasnier, Patrick Delaunay, Marek Vasut, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

This command can be used to print the current parser with 'parser print'.
It can also be used to set the current parser with 'parser set'.
For the moment, only one value is valid for set: old.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 cmd/Makefile |   2 +
 cmd/parser.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++
 common/cli.c |   3 +-
 3 files changed, 124 insertions(+), 1 deletion(-)
 create mode 100644 cmd/parser.c

diff --git a/cmd/Makefile b/cmd/Makefile
index 36d2daf22a..659a64371a 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -220,6 +220,8 @@ obj-$(CONFIG_CMD_AVB) += avb.o
 # Foundries.IO SCP03
 obj-$(CONFIG_CMD_SCP03) += scp03.o
 
+obj-$(CONFIG_HUSH_PARSER) += parser.o
+
 obj-$(CONFIG_ARM) += arm/
 obj-$(CONFIG_RISCV) += riscv/
 obj-$(CONFIG_SANDBOX) += sandbox/
diff --git a/cmd/parser.c b/cmd/parser.c
new file mode 100644
index 0000000000..0a433b7dc6
--- /dev/null
+++ b/cmd/parser.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <common.h>
+#include <cli.h>
+#include <command.h>
+#include <string.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static const char *gd_flags_to_parser(void)
+{
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
+		return "old";
+	return NULL;
+}
+
+static int do_parser_get(struct cmd_tbl *cmdtp, int flag, int argc,
+			 char *const argv[])
+{
+	const char *current = gd_flags_to_parser();
+
+	if (!current) {
+		printf("current parser value is not valid, this should not happen!\n");
+		return CMD_RET_FAILURE;
+	}
+
+	printf("%s\n", current);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int parser_string_to_gd_flags(const char *parser)
+{
+	if (!strcmp(parser, "old"))
+		return GD_FLG_HUSH_OLD_PARSER;
+	return -1;
+}
+
+static void reset_parser_gd_flags(void)
+{
+	gd->flags &= ~GD_FLG_HUSH_OLD_PARSER;
+}
+
+static int do_parser_set(struct cmd_tbl *cmdtp, int flag, int argc,
+			      char *const argv[])
+{
+	char *parser_name;
+	int parser_flag;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	parser_name = argv[1];
+
+	parser_flag = parser_string_to_gd_flags(parser_name);
+	if (parser_flag == -1) {
+		printf("Bad value for parser name: %s\n", parser_name);
+		return CMD_RET_USAGE;
+	}
+
+	if (parser_flag == GD_FLG_HUSH_OLD_PARSER &&
+		!CONFIG_IS_ENABLED(HUSH_OLD_PARSER)) {
+		printf("Want to set current parser to old, but its code was not compiled!\n");
+		return CMD_RET_FAILURE;
+	}
+
+	if (parser_flag == GD_FLG_HUSH_2021_PARSER &&
+		!CONFIG_IS_ENABLED(HUSH_2021_PARSER)) {
+		printf("Want to set current parser to 2021, but its code was not compiled!\n");
+		return CMD_RET_FAILURE;
+	}
+
+	reset_parser_gd_flags();
+	gd->flags |= parser_flag;
+
+	cli_init();
+	cli_loop();
+
+	/* cli_loop() should never return. */
+	return CMD_RET_FAILURE;
+}
+
+static struct cmd_tbl parser_sub[] = {
+	U_BOOT_CMD_MKENT(get, 1, 1, do_parser_get, "", ""),
+	U_BOOT_CMD_MKENT(set, 2, 1, do_parser_set, "", ""),
+};
+
+static int do_parser(struct cmd_tbl *cmdtp, int flag, int argc,
+		     char *const argv[])
+{
+	struct cmd_tbl *cp;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	/* drop initial "parser" arg */
+	argc--;
+	argv++;
+
+	cp = find_cmd_tbl(argv[0], parser_sub, ARRAY_SIZE(parser_sub));
+	if (cp)
+		return cp->cmd(cmdtp, flag, argc, argv);
+
+	return CMD_RET_USAGE;
+}
+
+#if CONFIG_IS_ENABLED(SYS_LONGHELP)
+static char parser_help_text[] =
+	"get - print current parser\n"
+	"set - set the current parser, possible value is: old"
+	;
+#endif
+
+U_BOOT_CMD(parser, 3, 1, do_parser,
+	   "parser",
+#if CONFIG_IS_ENABLED(SYS_LONGHELP)
+	   parser_help_text
+#endif
+);
diff --git a/common/cli.c b/common/cli.c
index 5eef0cad8c..21f98ca99c 100644
--- a/common/cli.c
+++ b/common/cli.c
@@ -255,7 +255,8 @@ void cli_loop(void)
 void cli_init(void)
 {
 #ifdef CONFIG_HUSH_PARSER
-	if (!(gd->flags & GD_FLG_HUSH_OLD_PARSER))
+	if (!(gd->flags & GD_FLG_HUSH_OLD_PARSER)
+		&& CONFIG_IS_ENABLED(HUSH_OLD_PARSER))
 		gd->flags |= GD_FLG_HUSH_OLD_PARSER;
 	u_boot_hush_start();
 #endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (10 preceding siblings ...)
  2023-03-30 19:46 ` [RFC PATCH v7 11/23] cmd: Add new parser command Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 13/23] cli: hush_2021: Enable variables expansion for hush 2021 Francis Laniel
                   ` (10 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Alexey Romanov,
	Marek Vasut, Patrick Delaunay, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Linus Walleij, Andrew Scull

If one defines HUSH_2021_PARSER, it is then possible to use 2021 parser with:
=> parser get
old
=> parser set 2021
2021> parser get
2021

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 cmd/parser.c                      |  7 ++++-
 common/cli.c                      | 38 +++++++++++++++++++----
 common/cli_hush_2021.c            |  3 ++
 common/cli_hush_upstream.c        | 46 +++++++++++++++++++++++++---
 include/asm-generic/global_data.h |  4 +++
 include/cli_hush.h                | 51 +++++++++++++++++++++++++++++--
 6 files changed, 135 insertions(+), 14 deletions(-)

diff --git a/cmd/parser.c b/cmd/parser.c
index 0a433b7dc6..f7b0f0dc9d 100644
--- a/cmd/parser.c
+++ b/cmd/parser.c
@@ -12,6 +12,8 @@ static const char *gd_flags_to_parser(void)
 {
 	if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
 		return "old";
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER)
+		return "2021";
 	return NULL;
 }
 
@@ -34,12 +36,15 @@ static int parser_string_to_gd_flags(const char *parser)
 {
 	if (!strcmp(parser, "old"))
 		return GD_FLG_HUSH_OLD_PARSER;
+	if (!strcmp(parser, "2021"))
+		return GD_FLG_HUSH_2021_PARSER;
 	return -1;
 }
 
 static void reset_parser_gd_flags(void)
 {
 	gd->flags &= ~GD_FLG_HUSH_OLD_PARSER;
+	gd->flags &= ~GD_FLG_HUSH_2021_PARSER;
 }
 
 static int do_parser_set(struct cmd_tbl *cmdtp, int flag, int argc,
@@ -108,7 +113,7 @@ static int do_parser(struct cmd_tbl *cmdtp, int flag, int argc,
 #if CONFIG_IS_ENABLED(SYS_LONGHELP)
 static char parser_help_text[] =
 	"get - print current parser\n"
-	"set - set the current parser, possible value is: old"
+	"set - set the current parser, possible values are: old, 2021"
 	;
 #endif
 
diff --git a/common/cli.c b/common/cli.c
index 21f98ca99c..e75a3ccc50 100644
--- a/common/cli.c
+++ b/common/cli.c
@@ -40,12 +40,15 @@ int run_command(const char *cmd, int flag)
 		return 1;
 
 	return 0;
-#else
+#elif CONFIG_IS_ENABLED(HUSH_OLD_PARSER)
 	int hush_flags = FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP;
 
 	if (flag & CMD_FLAG_ENV)
 		hush_flags |= FLAG_CONT_ON_NEWLINE;
 	return parse_string_outer(cmd, hush_flags);
+#else /* HUSH_2021_PARSER */
+	/* Not yet implemented. */
+	return 1;
 #endif
 }
 
@@ -105,7 +108,12 @@ int run_command_list(const char *cmd, int len, int flag)
 		buff[len] = '\0';
 	}
 #ifdef CONFIG_HUSH_PARSER
+#if CONFIG_IS_ENABLED(HUSH_OLD_PARSER)
 	rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
+#else /* HUSH_2021_PARSER */
+	/* Not yet implemented. */
+	rcode = 1;
+#endif
 #else
 	/*
 	 * This function will overwrite any \n it sees with a \0, which
@@ -241,8 +249,13 @@ err:
 void cli_loop(void)
 {
 	bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);
-#ifdef CONFIG_HUSH_PARSER
-	parse_file_outer();
+#if CONFIG_IS_ENABLED(HUSH_PARSER)
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER)
+		parse_and_run_file();
+	else if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
+		parse_file_outer();
+
+	printf("Problem\n");
 	/* This point is never reached */
 	for (;;);
 #elif defined(CONFIG_CMDLINE)
@@ -255,10 +268,23 @@ void cli_loop(void)
 void cli_init(void)
 {
 #ifdef CONFIG_HUSH_PARSER
+	/* This if block is used to initialize hush parser gd flag. */
 	if (!(gd->flags & GD_FLG_HUSH_OLD_PARSER)
-		&& CONFIG_IS_ENABLED(HUSH_OLD_PARSER))
-		gd->flags |= GD_FLG_HUSH_OLD_PARSER;
-	u_boot_hush_start();
+		&& !(gd->flags & GD_FLG_HUSH_2021_PARSER)) {
+		if (CONFIG_IS_ENABLED(HUSH_OLD_PARSER))
+			gd->flags |= GD_FLG_HUSH_OLD_PARSER;
+		else if (CONFIG_IS_ENABLED(HUSH_2021_PARSER))
+			gd->flags |= GD_FLG_HUSH_2021_PARSER;
+	}
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		u_boot_hush_start();
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		u_boot_hush_start_2021();
+	} else {
+		printf("No valid hush parser to use, cli will not initialized!\n");
+		return;
+	}
 #endif
 
 #if defined(CONFIG_HUSH_INIT_VAR)
diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
index 6d109933b8..653ea52929 100644
--- a/common/cli_hush_2021.c
+++ b/common/cli_hush_2021.c
@@ -221,6 +221,9 @@ static uint8_t xfunc_error_retval;
 static const char defifsvar[] __aligned(1) = "IFS= \t\n";
 #define defifs (defifsvar + 4)
 
+/* This define is used to check if exit command was called. */
+#define EXIT_RET_CODE -2
+
 /*
  * This define is used for changes that need be done directly in the upstream
  * sources still. Ideally, its use should be minimized as much as possible.
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index cc64af4e0c..84227a248e 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -7914,7 +7914,17 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
 		}
 		debug_print_tree(pipe_list, 0);
 		debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
+#ifndef __U_BOOT__
 		run_and_free_list(pipe_list);
+#else /* __U_BOOT__ */
+		int rcode = run_and_free_list(pipe_list);
+		/*
+		 * We reset input string to not run the following command, so running
+		 * 'exit; echo foo' does not print foo.
+		 */
+		if (rcode <= EXIT_RET_CODE)
+			setup_file_in_str(inp);
+#endif /* __U_BOOT__ */
 		empty = 0;
 		if (G_flag_return_in_progress == 1)
 			break;
@@ -10369,13 +10379,39 @@ static int run_list(struct pipe *pi)
 #endif /* !__U_BOOT__ */
 		rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
 #ifdef __U_BOOT__
-		if (r == -2) {
-			/* -2 indicates exit was called, so we need to quit now. */
-			G.last_exitcode = rcode;
+		if (r <= EXIT_RET_CODE) {
+			int previous_rcode = G.last_exitcode;
+			/*
+			 * This magic is to get the exit code given by the user.
+			 * Contrary to old shell code, we use + EXIT_RET_CODE as EXIT_RET_CODE
+			 * equals -2.
+			 */
+			G.last_exitcode = -r + EXIT_RET_CODE;
 
-			break;
+			/*
+			 * This case deals with the following:
+			 * => setenv inner 'echo entry inner; exit; echo inner done'
+			 * => setenv outer 'echo entry outer; run inner; echo outer done'
+			 * => run outer
+			 * So, if we are in inner, we need to break and not run the other
+			 * commands.
+			 * Otherwise, we just continue in outer.
+			 * As return code are propagated, we use the previous value to check if
+			 * exit was just called or was propagated.
+			 */
+			if (previous_rcode != r) {
+				/*
+				 * If run from run_command, run_command_flags will be set, so we check
+				 * this to know if we are in main input shell.
+				 */
+				if (!G.run_command_flags)
+					printf("exit not allowed from main input shell.\n");
+
+				break;
+			}
+			continue;
 		}
-#endif
+#endif /* __U_BOOT__ */
 		if (r != -1) {
 			/* We ran a builtin, function, or group.
 			 * rcode is already known
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index d61aa4346d..3bdc645a05 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -654,6 +654,10 @@ enum gd_flags {
 	 * @GD_FLG_HUSH_OLD_PARSER: Use hush old parser.
 	 */
 	GD_FLG_HUSH_OLD_PARSER = 0x200000,
+	/**
+	 * @GD_FLG_HUSH_2021_PARSER: Use hush 2021 parser.
+	 */
+	GD_FLG_HUSH_2021_PARSER = 0x400000,
 };
 
 #endif /* __ASSEMBLY__ */
diff --git a/include/cli_hush.h b/include/cli_hush.h
index 2bd35670c7..4ef79de53c 100644
--- a/include/cli_hush.h
+++ b/include/cli_hush.h
@@ -12,11 +12,58 @@
 #define FLAG_REPARSING       (1 << 2)	  /* >=2nd pass */
 #define FLAG_CONT_ON_NEWLINE (1 << 3)	  /* continue when we see \n */
 
+#if CONFIG_IS_ENABLED(HUSH_OLD_PARSER)
 extern int u_boot_hush_start(void);
-extern int parse_string_outer(const char *, int);
+extern int parse_string_outer(const char *str, int flag);
 extern int parse_file_outer(void);
-
 int set_local_var(const char *s, int flg_export);
+#else
+static inline int u_boot_hush_start(void)
+{
+	return 0;
+}
+
+static inline int parse_string_outer(const char *str, int flag)
+{
+	return 1;
+}
+
+static inline int parse_file_outer(void)
+{
+	return 0;
+}
+
+static inline int set_local_var(const char *s, int flg_export)
+{
+	return 0;
+}
+#endif
+#if CONFIG_IS_ENABLED(HUSH_2021_PARSER)
+extern int u_boot_hush_start_2021(void);
+extern int parse_string_outer_2021(const char *str, int flag);
+extern void parse_and_run_file(void);
+int set_local_var_2021(char *s, int flg_export);
+#else
+static inline int u_boot_hush_start_2021(void)
+{
+	return 0;
+}
+
+static inline int parse_string_outer_2021(const char *str, int flag)
+{
+	return 1;
+}
+
+static inline void parse_and_run_file(void)
+{
+}
+
+static inline int set_local_var_2021(char *s, int flg_export)
+{
+	return 0;
+}
+#endif
+
 void unset_local_var(const char *name);
 char *get_local_var(const char *s);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 13/23] cli: hush_2021: Enable variables expansion for hush 2021
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (11 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 14/23] cli: hush_2021: Add functions to be called from run_command() Francis Laniel
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrice Chotard, Patrick Delaunay, Fabrice Gasnier, Marek Vasut,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Enables variables expansion for hush 2021, both for local and environment
variables.
So the following commands:
foo=bar
echo $foo
setenv bar foo
echo $bar
leads to "bar" and "foo" being printed on console output.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_2021.c     | 17 +++++++
 common/cli_hush_upstream.c | 91 +++++++++++++++++++++++++++++++++++---
 2 files changed, 103 insertions(+), 5 deletions(-)

diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
index 653ea52929..7dd30ea0ef 100644
--- a/common/cli_hush_2021.c
+++ b/common/cli_hush_2021.c
@@ -207,6 +207,23 @@ static const char* endofname(const char *name)
 	return name;
 }
 
+/**
+ * list_size() - returns the number of elements in char ** before NULL.
+ *
+ * Argument must contain NULL to signalize its end.
+ *
+ * @list The list to count the number of element.
+ * @return The number of element in list.
+ */
+static size_t list_size(char **list)
+{
+	size_t size;
+
+	for (size = 0; list[size] != NULL; size++);
+
+	return size;
+}
+
 struct in_str;
 static int u_boot_cli_readline(struct in_str *i);
 
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index 84227a248e..b806c5c653 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -3486,7 +3486,6 @@ static int o_get_last_ptr(o_string *o, int n)
 	return ((int)(uintptr_t)list[n-1]) + string_start;
 }
 
-#ifndef __U_BOOT__
 /*
  * Globbing routines.
  *
@@ -3741,8 +3740,10 @@ static int glob_needed(const char *s)
  */
 static int perform_glob(o_string *o, int n)
 {
+#ifndef __U_BOOT__
 	glob_t globdata;
 	int gr;
+#endif /* __U_BOOT__ */
 	char *pattern;
 
 	debug_printf_glob("start perform_glob: n:%d o->data:%p\n", n, o->data);
@@ -3751,13 +3752,16 @@ static int perform_glob(o_string *o, int n)
 	pattern = o->data + o_get_last_ptr(o, n);
 	debug_printf_glob("glob pattern '%s'\n", pattern);
 	if (!glob_needed(pattern)) {
+#ifndef __U_BOOT__
  literal:
+#endif /* __U_BOOT__ */
 		/* unbackslash last string in o in place, fix length */
 		o->length = unbackslash(pattern) - o->data;
 		debug_printf_glob("glob pattern '%s' is literal\n", pattern);
 		return o_save_ptr_helper(o, n);
 	}
 
+#ifndef __U_BOOT__
 	memset(&globdata, 0, sizeof(globdata));
 	/* Can't use GLOB_NOCHECK: it does not unescape the string.
 	 * If we glob "*.\*" and don't find anything, we need
@@ -3793,16 +3797,22 @@ static int perform_glob(o_string *o, int n)
 	if (DEBUG_GLOB)
 		debug_print_list("perform_glob returning", o, n);
 	return n;
+#else /* __U_BOOT__ */
+	/*
+	 * NOTE We only use perform glob to call unbackslash to remove backslash
+	 * from string once expanded.
+	 * So, it seems OK to return this if no previous return was done.
+	 */
+	return o_save_ptr_helper(o, n);
+#endif /* __U_BOOT__ */
 }
 
-#endif /* !__U_BOOT__ */
 #endif /* !HUSH_BRACE_EXPANSION */
 
 /* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered.
  * Otherwise, just finish current list[] and start new */
 static int o_save_ptr(o_string *o, int n)
 {
-#ifndef __U_BOOT__
 	if (o->o_expflags & EXP_FLAG_GLOB) {
 		/* If o->has_empty_slot, list[n] was already globbed
 		 * (if it was requested back then when it was filled)
@@ -3810,7 +3820,6 @@ static int o_save_ptr(o_string *o, int n)
 		if (!o->has_empty_slot)
 			return perform_glob(o, n); /* o_save_ptr_helper is inside */
 	}
-#endif /* !__U_BOOT__ */
 	return o_save_ptr_helper(o, n);
 }
 
@@ -5456,7 +5465,20 @@ static int parse_dollar(o_string *as_string,
 			nommu_addchr(as_string, ch);
 			if (ch == '}')
 				break;
+#ifndef __U_BOOT__
 			if (!isalnum(ch) && ch != '_') {
+#else /* __U_BOOT__ */
+			/*
+			 * In several places in U-Boot, we use variable like
+			 * foo# (e.g. serial#), particularly in env.
+			 * So, we need to authorize # to appear inside
+			 * variable name and then expand this variable.
+			 * NOTE Having # in variable name is not permitted in
+			 * upstream hush but expansion will be done (even though
+			 * the result will be empty).
+			 */
+			if (!isalnum(ch) && ch != '_' && ch != '#') {
+#endif /* __U_BOOT__ */
 				unsigned end_ch;
 #ifndef __U_BOOT__
 				unsigned char last_ch;
@@ -7031,7 +7053,20 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 		}
 #endif /* !__U_BOOT__ */
 		default:
+#ifndef __U_BOOT__
 			val = get_local_var_value(var);
+#else /* __U_BOOT__ */
+			/*
+			 * Environment variable set with setenv* have to be
+			 * expanded.
+			 * So, we first search if the variable exists in
+			 * environment, if this is not the case, we default to
+			 * local value.
+			 */
+			val = env_get(var);
+			if (!val)
+				val = get_local_var_value(var);
+#endif /* __U_BOOT__ */
 		}
 	}
 
@@ -7376,7 +7411,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 		case '*':
 		case '@': {
 			int i;
+#ifndef __U_BOOT__
 			if (!G.global_argv[1])
+#else /* __U_BOOT__ */
+			if (!G.global_argv || !G.global_argv[1])
+#endif /* __U_BOOT__ */
 				break;
 			i = 1;
 			cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
@@ -9977,7 +10016,30 @@ static NOINLINE int run_pipe(struct pipe *pi)
 #endif /* !__U_BOOT__ */
 		command = &pi->cmds[cmd_no];
 		cmd_no++;
-		if (command->argv) {
+
+#ifdef __U_BOOT__
+		/* Replace argv and argc by expanded if it exists. */
+		if (argv_expanded) {
+			/*
+			 * We need to save a pointer to argv, we will restore it
+			 * later, so it will be freed when pipe is freed.
+			 */
+			argv = command->argv;
+
+			/*
+			 * After expansion, there can be more or less argument, so we need to
+			 * update argc, for example:
+			 * - More arguments:
+			 *   foo='bar quuz'
+			 *   echo $foo
+			 * - Less arguments:
+			 *   echo $foo (if foo was never set)
+			 */
+			command->argc = list_size(argv_expanded);
+			command->argv = argv_expanded;
+		}
+#endif /* __U_BOOT__ */
+			if (command->argv) {
 			debug_printf_exec(": pipe member '%s' '%s'...\n",
 					command->argv[0], command->argv[1]);
 		} else {
@@ -10092,6 +10154,25 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		rcode = cmd_process(G.do_repeat ? CMD_FLAG_REPEAT : 0,
 				    command->argc, command->argv,
 				    &(G.flag_repeat), NULL);
+
+		if (argv_expanded) {
+			/*
+			 * expand_strvec_to_strvec() allocates memory to expand
+			 * argv, we need to free it.
+			 */
+			free(argv_expanded);
+
+			/*
+			 * We also restore command->argv to its original value
+			 * so no memory leak happens.
+			 */
+			command->argv = argv;
+
+			/*
+			 * NOTE argc exists only in U-Boot, so argv freeing does
+			 * not rely on it as this code exists in BusyBox.
+			 */
+		}
 #endif /* __U_BOOT__ */
 	}
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 14/23] cli: hush_2021: Add functions to be called from run_command()
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (12 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 13/23] cli: hush_2021: Enable variables expansion for hush 2021 Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*() Francis Laniel
                   ` (8 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Alexey Romanov, Linus Walleij,
	Marek Vasut, Fabrice Gasnier, Patrice Chotard, Patrick Delaunay,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

run_command() is called internally by the command run and it can also be called
directly from U-Boot code, e.g. to do unit tests.
This commit adds this path to go to hush 2021.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_upstream.c | 66 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 3 deletions(-)

diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index b806c5c653..b11445c3ac 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -1012,6 +1012,7 @@ struct globals {
 #ifdef __U_BOOT__
 	int flag_repeat;
 	int do_repeat;
+	int run_command_flags;
 #endif /* __U_BOOT__ */
 	char *ifs_whitespace; /* = G.ifs or malloced */
 #ifndef __U_BOOT__
@@ -3005,7 +3006,24 @@ static int i_getch(struct in_str *i)
 	if (i->p && *i->p != '\0') {
 		ch = (unsigned char)*i->p++;
 		goto out;
+#ifndef __U_BOOT__
 	}
+#else /* __U_BOOT__ */
+	/*
+	 * There are two ways for command to be called:
+	 * 1. The first one is when they are typed by the user.
+	 * 2. The second one is through run_command() (NOTE command run
+	 * internally calls run_command()).
+	 *
+	 * In the second case, we do not get input from the user, so once we
+	 * get a '\0', it means we need to stop.
+	 * NOTE G.run_command_flags is only set on run_command call stack, so
+	 * we use this to know if we come from user input or run_command().
+	 */
+	} else if (i->p && *i->p == '\0' && G.run_command_flags){
+		return EOF;
+	}
+#endif /* __U_BOOT__ */
 #endif
 #ifndef __U_BOOT__
 	/* peek_buf[] is an int array, not char. Can contain EOF. */
@@ -3164,7 +3182,6 @@ static void setup_file_in_str(struct in_str *i)
 #endif /* !__U_BOOT__ */
 }
 
-#ifndef __U_BOOT__
 static void setup_string_in_str(struct in_str *i, const char *s)
 {
 	memset(i, 0, sizeof(*i));
@@ -3172,7 +3189,6 @@ static void setup_string_in_str(struct in_str *i, const char *s)
 	i->p = s;
 }
 
-#endif /* !__U_BOOT__ */
 
 /*
  * o_string support
@@ -7911,7 +7927,11 @@ static int run_and_free_list(struct pipe *pi);
  * NUL: parse all, execute, return
  * ';': parse till ';' or newline, execute, repeat till EOF
  */
+#ifndef __U_BOOT__
 static void parse_and_run_stream(struct in_str *inp, int end_trigger)
+#else /* __U_BOOT__ */
+static int parse_and_run_stream(struct in_str *inp, int end_trigger)
+#endif /* __U_BOOT__ */
 {
 	/* Why we need empty flag?
 	 * An obscure corner case "false; ``; echo $?":
@@ -7920,7 +7940,11 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
 	 * this breaks "false; echo `echo $?`" case.
 	 */
 	bool empty = 1;
+#ifndef __U_BOOT__
 	while (1) {
+#else /* __U_BOOT__ */
+	do {
+#endif /* __U_BOOT__ */
 		struct pipe *pipe_list;
 
 #if ENABLE_HUSH_INTERACTIVE
@@ -7967,21 +7991,57 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
 		empty = 0;
 		if (G_flag_return_in_progress == 1)
 			break;
+#ifndef __U_BOOT__
 	}
+#else /* __U_BOOT__ */
+	/*
+	 * This do/while is needed by run_command to avoid looping on a command
+	 * with syntax error.
+	 */
+	} while (!(G.run_command_flags & FLAG_EXIT_FROM_LOOP));
+
+	return G.last_exitcode;
+#endif /* __U_BOOT__ */
 }
 
 #ifndef __U_BOOT__
 static void parse_and_run_string(const char *s)
+#else /* __U_BOOT__ */
+static int parse_and_run_string(const char *s)
+#endif /* __U_BOOT__ */
 {
 	struct in_str input;
 	//IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
 
 	setup_string_in_str(&input, s);
+#ifndef __U_BOOT__
 	parse_and_run_stream(&input, '\0');
+#else /* __U_BOOT__ */
+	return parse_and_run_stream(&input, '\0');
+#endif /* __U_BOOT__ */
 	//IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
 }
-#endif /* !__U_BOOT__ */
 
+#ifdef __U_BOOT__
+int parse_string_outer(const char *cmd, int flags)
+{
+	int ret;
+	int old_flags;
+
+	/*
+	 * Keep old values of run_command to be able to restore them once
+	 * command was executed.
+	 */
+	old_flags = G.run_command_flags;
+	G.run_command_flags = flags;
+
+	ret = parse_and_run_string(cmd);
+
+	G.run_command_flags = old_flags;
+
+	return ret;
+}
+#endif /* __U_BOOT__ */
 #ifndef __U_BOOT__
 static void parse_and_run_file(HFILE *fp)
 #else /* __U_BOOT__ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*()
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (13 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 14/23] cli: hush_2021: Add functions to be called from run_command() Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021 Francis Laniel
                   ` (7 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Marek Vasut, Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Enables using, in code, hush 2021 as parser for run_command function family.
It also enables the command run to be used by CLI user of hush 2021.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 common/cli.c               | 63 ++++++++++++++++++++++++++++----------
 common/cli_hush_upstream.c |  2 +-
 2 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/common/cli.c b/common/cli.c
index e75a3ccc50..4575d4c898 100644
--- a/common/cli.c
+++ b/common/cli.c
@@ -40,14 +40,31 @@ int run_command(const char *cmd, int flag)
 		return 1;
 
 	return 0;
-#elif CONFIG_IS_ENABLED(HUSH_OLD_PARSER)
-	int hush_flags = FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP;
-
-	if (flag & CMD_FLAG_ENV)
-		hush_flags |= FLAG_CONT_ON_NEWLINE;
-	return parse_string_outer(cmd, hush_flags);
-#else /* HUSH_2021_PARSER */
-	/* Not yet implemented. */
+#else
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		int hush_flags = FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP;
+
+		if (flag & CMD_FLAG_ENV)
+			hush_flags |= FLAG_CONT_ON_NEWLINE;
+		return parse_string_outer(cmd, hush_flags);
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * Possible values for flags are the following:
+		 * FLAG_EXIT_FROM_LOOP: This flags ensures we exit from loop in
+		 * parse_and_run_stream() after first iteration while normal
+		 * behavior, * i.e. when called from cli_loop(), is to loop
+		 * infinitely.
+		 * FLAG_PARSE_SEMICOLON: Hush 2021 parses ';' and does not stop
+		 * first time it sees one. So, I think we do not need this flag.
+		 * FLAG_REPARSING: For the moment, I do not understand the goal
+		 * of this flag.
+		 * FLAG_CONT_ON_NEWLINE: This flag seems to be used to continue
+		 * parsing even when reading '\n' when coming from
+		 * run_command(). In this case, Hush 2021 reads until it finds
+		 * '\0'. So, I think we do not need this flag.
+		 */
+		return parse_string_outer_2021(cmd, FLAG_EXIT_FROM_LOOP);
+	}
 	return 1;
 #endif
 }
@@ -64,12 +81,25 @@ int run_command_repeatable(const char *cmd, int flag)
 #ifndef CONFIG_HUSH_PARSER
 	return cli_simple_run_command(cmd, flag);
 #else
+	int ret;
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ret = parse_string_outer(cmd,
+					 FLAG_PARSE_SEMICOLON
+					 | FLAG_EXIT_FROM_LOOP);
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		ret = parse_string_outer_2021(cmd,
+					      FLAG_PARSE_SEMICOLON
+					      | FLAG_EXIT_FROM_LOOP);
+	} else {
+		ret = 1;
+	}
+
 	/*
 	 * parse_string_outer() returns 1 for failure, so clean up
 	 * its result.
 	 */
-	if (parse_string_outer(cmd,
-			       FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP))
+	if (ret)
 		return -1;
 
 	return 0;
@@ -108,12 +138,13 @@ int run_command_list(const char *cmd, int len, int flag)
 		buff[len] = '\0';
 	}
 #ifdef CONFIG_HUSH_PARSER
-#if CONFIG_IS_ENABLED(HUSH_OLD_PARSER)
-	rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
-#else /* HUSH_2021_PARSER */
-	/* Not yet implemented. */
-	rcode = 1;
-#endif
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		rcode = parse_string_outer_2021(buff, FLAG_PARSE_SEMICOLON);
+	} else {
+		rcode = 1;
+	}
 #else
 	/*
 	 * This function will overwrite any \n it sees with a \0, which
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index b11445c3ac..dbf77ad503 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -8023,7 +8023,7 @@ static int parse_and_run_string(const char *s)
 }
 
 #ifdef __U_BOOT__
-int parse_string_outer(const char *cmd, int flags)
+int parse_string_outer_2021(const char *cmd, int flags)
 {
 	int ret;
 	int old_flags;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (14 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*() Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 17/23] test: hush: Fix variable expansion " Francis Laniel
                   ` (6 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Sughosh Ganu, Linus Walleij,
	Alexey Romanov, Patrick Delaunay, Patrice Chotard,
	Fabrice Gasnier, Hector Palacios, Marek Vasut, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Andrew Scull

Modifies the expected result for hush 2021.
Indeed, there were bugs in actual U-Boot hush which were fixed in upstream
Busybox.
As hush 2021 is based on upstream Busybox, these bugs no longer exist.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 test/hush/list.c | 69 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 4 deletions(-)

diff --git a/test/hush/list.c b/test/hush/list.c
index 052cf2783c..6f8f10f15e 100644
--- a/test/hush/list.c
+++ b/test/hush/list.c
@@ -9,6 +9,7 @@
 #include <env_attr.h>
 #include <test/hush.h>
 #include <test/ut.h>
+#include <asm/global_data.h>
 
 static int hush_test_semicolon(struct unit_test_state *uts)
 {
@@ -46,12 +47,43 @@ static int hush_test_or(struct unit_test_state *uts)
 }
 HUSH_TEST(hush_test_or, 0);
 
+DECLARE_GLOBAL_DATA_PTR;
+
 static int hush_test_and_or(struct unit_test_state *uts)
 {
 	/* A && B || C truth table. */
 	ut_asserteq(1, run_command("false && false || false", 0));
-	ut_asserteq(1, run_command("false && false || true", 0));
-	ut_asserteq(1, run_command("false && true || true", 0));
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_asserteq(1, run_command("false && false || true", 0));
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * This difference seems to come from a bug solved in Busybox
+		 * hush.
+		 *
+		 * Indeed, the following expression can be seen like this:
+		 * (false && false) || true
+		 * So, (false && false) returns 1, the second false is not
+		 * executed, and true is executed because of ||.
+		 */
+		ut_assertok(run_command("false && false || true", 0));
+	}
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_asserteq(1, run_command("false && true || true", 0));
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * This difference seems to come from a bug solved in Busybox
+		 * hush.
+		 *
+		 * Indeed, the following expression can be seen like this:
+		 * (false && true) || true
+		 * So, (false && true) returns 1, the true is not executed, and
+		 * true is executed because of ||.
+		 */
+		ut_assertok(run_command("false && true || true", 0));
+	}
+
 	ut_asserteq(1, run_command("false && true || false", 0));
 	ut_assertok(run_command("true && true || false", 0));
 	ut_asserteq(1, run_command("true && false || false", 0));
@@ -69,8 +101,37 @@ static int hush_test_or_and(struct unit_test_state *uts)
 	ut_asserteq(1, run_command("false || false && true", 0));
 	ut_assertok(run_command("false || true && true", 0));
 	ut_asserteq(1, run_command("false || true && false", 0));
-	ut_assertok(run_command("true || true && false", 0));
-	ut_assertok(run_command("true || false && false", 0));
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_assertok(run_command("true || true && false", 0));
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * This difference seems to come from a bug solved in Busybox
+		 * hush.
+		 *
+		 * Indeed, the following expression can be seen like this:
+		 * (true || true) && false
+		 * So, (true || true) returns 0, the second true is not
+		 * executed, and then false is executed because of &&.
+		 */
+		ut_asserteq(1, run_command("true || true && false", 0));
+	}
+
+	if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_assertok(run_command("true || false && false", 0));
+	} else if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * This difference seems to come from a bug solved in Busybox
+		 * hush.
+		 *
+		 * Indeed, the following expression can be seen like this:
+		 * (true || false) && false
+		 * So, (true || false) returns 0, the false is not executed, and
+		 * then false is executed because of &&.
+		 */
+		ut_asserteq(1, run_command("true || false && false", 0));
+	}
+
 	ut_assertok(run_command("true || false && true", 0));
 	ut_assertok(run_command("true || true && true", 0));
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 17/23] test: hush: Fix variable expansion tests for hush 2021
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (15 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021 Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 18/23] cli: hush_2021: Enable using < and > as string compare operators Francis Laniel
                   ` (5 subsequent siblings)
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrick Delaunay, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Modifies the expected result for hush 2021.
Indeed, there were bugs in actual U-Boot hush which were fixed in upstream
Busybox.
As hush 2021 is based on upstream Busybox, these bugs no longer exist.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 test/hush/dollar.c | 79 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 69 insertions(+), 10 deletions(-)

diff --git a/test/hush/dollar.c b/test/hush/dollar.c
index 27feda2970..0ca799b6b1 100644
--- a/test/hush/dollar.c
+++ b/test/hush/dollar.c
@@ -9,6 +9,9 @@
 #include <env_attr.h>
 #include <test/hush.h>
 #include <test/ut.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
 
 static int hush_test_simple_dollar(struct unit_test_state *uts)
 {
@@ -51,13 +54,29 @@ static int hush_test_simple_dollar(struct unit_test_state *uts)
 	ut_asserteq(1, run_command("dollar_foo='bar quux", 0));
 	/* Next line contains error message */
 	ut_assert_skipline();
-	ut_assert_console_end();
+
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * For some strange reasons, the console is not empty after
+		 * running above command.
+		 * So, we reset it to not have side effects for other tests.
+		 */
+		console_record_reset_enable();
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_assert_console_end();
+	}
 
 	ut_asserteq(1, run_command("dollar_foo=bar quux\"", 0));
 	/* Two next lines contain error message */
 	ut_assert_skipline();
 	ut_assert_skipline();
-	ut_assert_console_end();
+
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* See above comments. */
+		console_record_reset_enable();
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_assert_console_end();
+	}
 
 	ut_assertok(run_command("dollar_foo='bar \"quux'", 0));
 
@@ -71,17 +90,35 @@ static int hush_test_simple_dollar(struct unit_test_state *uts)
 	 */
 	console_record_reset_enable();
 
-	ut_asserteq(1, run_command("dollar_foo=\"bar 'quux\"", 0));
-	/* Next line contains error message */
-	ut_assert_skipline();
-	ut_assert_console_end();
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * Old parser returns an error because it waits for closing
+		 * '\'', but this behavior is wrong as the '\'' is surrounded by
+		 * '"', so no need to wait for a closing one.
+		 */
+		ut_assertok(run_command("dollar_foo=\"bar 'quux\"", 0));
+
+		ut_assertok(run_command("echo $dollar_foo", 0));
+		ut_assert_nextline("bar 'quux");
+		ut_assert_console_end();
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_asserteq(1, run_command("dollar_foo=\"bar 'quux\"", 0));
+		/* Next line contains error message */
+		ut_assert_skipline();
+		ut_assert_console_end();
+	}
 
 	ut_assertok(run_command("dollar_foo='bar quux'", 0));
 	ut_assertok(run_command("echo $dollar_foo", 0));
 	ut_assert_nextline("bar quux");
 	ut_assert_console_end();
 
-	puts("Beware: this test set local variable dollar_foo and it cannot be unset!");
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* Reset local variable. */
+		ut_assertok(run_command("dollar_foo=", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		puts("Beware: this test set local variable dollar_foo and it cannot be unset!");
+	}
 
 	return 0;
 }
@@ -109,7 +146,12 @@ static int hush_test_env_dollar(struct unit_test_state *uts)
 	/* Clean up setting the variable */
 	env_set("env_foo", NULL);
 
-	puts("Beware: this test set local variable env_foo and it cannot be unset!");
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* Reset local variable. */
+		ut_assertok(run_command("env_foo=", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		puts("Beware: this test set local variable env_foo and it cannot be unset!");
+	}
 
 	return 0;
 }
@@ -144,7 +186,18 @@ static int hush_test_command_dollar(struct unit_test_state *uts)
 	ut_assertok(run_command("dollar_bar='echo bar\\n'", 0));
 
 	ut_assertok(run_command("$dollar_bar", 0));
-	ut_assert_nextline("barn");
+
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * This difference seems to come from a bug solved in Busybox
+		 * hush.
+		 * Behavior of hush 2021 is coherent with bash and other shells.
+		 */
+		ut_assert_nextline("bar\\n");
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		ut_assert_nextline("barn");
+	}
+
 	ut_assert_console_end();
 
 	ut_assertok(run_command("dollar_bar='echo $bar'", 0));
@@ -160,7 +213,13 @@ static int hush_test_command_dollar(struct unit_test_state *uts)
 	ut_assert_nextline("quux");
 	ut_assert_console_end();
 
-	puts("Beware: this test sets local variable dollar_bar and dollar_quux and they cannot be unset!");
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* Reset local variables. */
+		ut_assertok(run_command("dollar_bar=", 0));
+		ut_assertok(run_command("dollar_quux=", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		puts("Beware: this test sets local variable dollar_bar and dollar_quux and they cannot be unset!");
+	}
 
 	return 0;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 18/23] cli: hush_2021: Enable using < and > as string compare operators
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (16 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 17/23] test: hush: Fix variable expansion " Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 19/23] cli: hush_2021: Enable if keyword Francis Laniel
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Samuel Dionne-Riel,
	Alexey Romanov, Marek Vasut, Patrice Chotard, Patrick Delaunay,
	Fabrice Gasnier, Hector Palacios, Rasmus Villemoes,
	AKASHI Takahiro, Linus Walleij, Rui Miguel Silva, Andrew Scull

In Busybox hush, '<' and '>' are used as redirection operators.
For example, cat foo > bar will write content of file foo inside file bar.
In U-Boot, we do not have file system, so we can hardly redirect command output
inside a file.

But, in actual U-Boot hush, these operators ('<' and '>') are used as string
compare operators.
For example, test aaa < bbb returns 0 as aaa is before bbb in the dictionary.
Busybox hush also permits this, but operators need to be escaped ('\<' and
'\>').
Indeed, if escaping is needed it permits the developer to think about its code,
as in a lot of case, we want to compare integers (using '-lt' or '-gt') rather
than strings.

As testing in U-Boot is handled by the test command, we will stick with the
original behaviour and not adapt to Busybox one.

Nonetheless, if one day we decide to implement test with '[[ ]]', we will then
stick to upstream Busybox behavior.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_upstream.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index dbf77ad503..68a9a4bb72 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -6160,7 +6160,28 @@ static struct pipe *parse_stream(char **pstring,
 			if (parse_redirect(&ctx, redir_fd, redir_style, input))
 				goto parse_error_exitcode1;
 			continue; /* get next char */
-#endif /* !__U_BOOT__ */
+#else /* __U_BOOT__ */
+			/*
+			 * In U-Boot, '<' and '>' can be used in test command to test if
+			 * a string is, alphabetically, before or after another.
+			 * In 2021 Busybox hush, we will keep the same behavior and so not treat
+			 * them as redirection operator.
+			 *
+			 * Indeed, in U-Boot, tests are handled by the test command and not by the
+			 * shell code.
+			 * So, better to give this character as input to test command.
+			 *
+			 * NOTE In my opinion, when you use '<' or '>' I am almost sure
+			 * you wanted to use "-gt" or "-lt" in place, so thinking to
+			 * escape these will make you should check your code (sh syntax
+			 * at this level is, for me, error prone).
+			 */
+			case '>':
+				fallthrough;
+			case '<':
+				o_addQchr(&ctx.word, ch);
+				continue;
+#endif /* __U_BOOT__ */
 		case '#':
 			if (ctx.word.length == 0 && !ctx.word.has_quoted_part) {
 				/* skip "#comment" */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 19/23] cli: hush_2021: Enable if keyword
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (17 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 18/23] cli: hush_2021: Enable using < and > as string compare operators Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 20/23] cli: hush_2021: Enable loops Francis Laniel
                   ` (3 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrice Chotard, Patrick Delaunay, Fabrice Gasnier,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Adds support for "if then else" construct both for command line interface and
through run_command().

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_2021.c     | 11 +++++++++++
 common/cli_hush_upstream.c | 12 ++++--------
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
index 7dd30ea0ef..aba5dcbbcd 100644
--- a/common/cli_hush_2021.c
+++ b/common/cli_hush_2021.c
@@ -33,6 +33,7 @@
  */
 #define ENABLE_HUSH_INTERACTIVE	1
 #define ENABLE_FEATURE_EDITING	1
+#define ENABLE_HUSH_IF		1
 /* No MMU in U-Boot */
 #define BB_MMU			0
 #define USE_FOR_NOMMU(...)	__VA_ARGS__
@@ -124,6 +125,11 @@ static void bb_error_msg(const char *s, ...)
 	va_end(p);
 }
 
+static void bb_simple_error_msg(const char *s)
+{
+	bb_error_msg("%s", s);
+}
+
 static void *xmalloc(size_t size)
 {
 	void *p = NULL;
@@ -147,6 +153,11 @@ static void *xrealloc(void *ptr, size_t size)
 	return p;
 }
 
+static void *xmemdup(const void *s, int n)
+{
+	return memcpy(xmalloc(n), s, n);
+}
+
 #define xstrdup		strdup
 #define xstrndup	strndup
 
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index 68a9a4bb72..9b65dcbde1 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -1488,7 +1488,6 @@ static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
 	die_if_script();
 }
 
-#ifndef __U_BOOT__
 static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
 {
 	if (msg)
@@ -1497,7 +1496,6 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
 		bb_simple_error_msg("syntax error");
 	die_if_script();
 }
-#endif /* !__U_BOOT__ */
 
 static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
 {
@@ -3962,7 +3960,6 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 		[PIPE_OR ] = "OR" ,
 		[PIPE_BG ] = "BG" ,
 	};
-#ifndef __U_BOOT__
 	static const char *RES[] = {
 		[RES_NONE ] = "NONE" ,
 # if ENABLE_HUSH_IF
@@ -3992,7 +3989,6 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 		[RES_XXXX ] = "XXXX" ,
 		[RES_SNTX ] = "SNTX" ,
 	};
-#endif /* !__U_BOOT__ */
 	static const char *const CMDTYPE[] = {
 		"{}",
 		"()",
@@ -4010,10 +4006,8 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 				lvl*2, "",
 				pin,
 				pi->num_cmds,
-#ifdef __U_BOOT__
 				(IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
 				RES[pi->res_word],
-#endif /* !__U_BOOT__ */
 				pi->followup, PIPE[pi->followup]
 		);
 		prn = 0;
@@ -9833,6 +9827,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		rcode = 1; /* exitcode if redir failed */
 #ifndef __U_BOOT__
 		if (setup_redirects(command, &squirrel) == 0) {
+#endif /* !__U_BOOT__ */
 			debug_printf_exec(": run_list\n");
 //FIXME: we need to pass squirrel down into run_list()
 //for SH_STANDALONE case, or else this construct:
@@ -9841,10 +9836,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
 //and in SH_STANDALONE mode, "find" is not execed,
 //therefore CLOEXEC on saved fd does not help.
 			rcode = run_list(command->group) & 0xff;
+#ifndef __U_BOOT__
 		}
 		restore_redirects(squirrel);
-		IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
 #endif /* !__U_BOOT__ */
+		IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
 		debug_leave();
 		debug_printf_exec("run_pipe: return %d\n", rcode);
 		return rcode;
@@ -10363,12 +10359,12 @@ static int run_list(struct pipe *pi)
 			break;
 		if (G_flag_return_in_progress == 1)
 			break;
+#endif /* !__U_BOOT__ */
 
 		IF_HAS_KEYWORDS(rword = pi->res_word;)
 		debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
 				rword, cond_code, last_rword);
 
-#endif /* !__U_BOOT__ */
 		sv_errexit_depth = G.errexit_depth;
 		if (
 #if ENABLE_HUSH_IF
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 20/23] cli: hush_2021: Enable loops
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (18 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 19/23] cli: hush_2021: Enable if keyword Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021 Francis Laniel
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Alexey Romanov, Marek Vasut,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

Enables the use of for, while and until loops for command line as
well as with run_command().

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 common/cli_hush_2021.c     |  1 +
 common/cli_hush_upstream.c | 15 ++++++++++++++-
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
index aba5dcbbcd..0a207d147b 100644
--- a/common/cli_hush_2021.c
+++ b/common/cli_hush_2021.c
@@ -34,6 +34,7 @@
 #define ENABLE_HUSH_INTERACTIVE	1
 #define ENABLE_FEATURE_EDITING	1
 #define ENABLE_HUSH_IF		1
+#define ENABLE_HUSH_LOOPS	1
 /* No MMU in U-Boot */
 #define BB_MMU			0
 #define USE_FOR_NOMMU(...)	__VA_ARGS__
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index 9b65dcbde1..23392939e1 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -10349,7 +10349,7 @@ static int run_list(struct pipe *pi)
 #ifndef __U_BOOT__
 	for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
 #else /* __U_BOOT__ */
-	for (; pi; pi = pi->next) {
+	for (; pi; pi = rword == RES_DONE ? loop_top : pi->next) {
 #endif /* __U_BOOT__ */
 		int r;
 		int sv_errexit_depth;
@@ -10451,7 +10451,20 @@ static int run_list(struct pipe *pi)
 			}
 			/* Insert next value from for_lcur */
 			/* note: *for_lcur already has quotes removed, $var expanded, etc */
+#ifndef __U_BOOT__
 			set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*flag:*/ 0);
+#else /* __U_BOOT__ */
+			/* We cannot use xasprintf, so we emulate it. */
+			char *full_var;
+			char *var = pi->cmds[0].argv[0];
+			char *val = *for_lcur++;
+
+			/* + 1 to take into account =. */
+			full_var = xmalloc(strlen(var) + strlen(val) + 1);
+			sprintf(full_var, "%s=%s", var, val);
+
+			set_local_var_2021(full_var, /*flag:*/ 0);
+#endif /* __U_BOOT__ */
 			continue;
 		}
 		if (rword == RES_IN) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (19 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 20/23] cli: hush_2021: Enable loops Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-04-01  6:32   ` Simon Glass
  2023-03-30 19:47 ` [RFC PATCH v7 22/23] cli: hush_2021: Add upstream commits up to 30th January 2023 Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy Francis Laniel
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrick Delaunay, Fabrice Gasnier, Patrice Chotard,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Modifies return code got from while loop as hush 2021 always returns 0 from
while loop.

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 test/hush/loop.c | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/test/hush/loop.c b/test/hush/loop.c
index ca777e38fe..add5402a32 100644
--- a/test/hush/loop.c
+++ b/test/hush/loop.c
@@ -9,6 +9,9 @@
 #include <env_attr.h>
 #include <test/hush.h>
 #include <test/ut.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
 
 static int hush_test_for(struct unit_test_state *uts)
 {
@@ -21,7 +24,12 @@ static int hush_test_for(struct unit_test_state *uts)
 	ut_assert_nextline("quux");
 	ut_assert_console_end();
 
-	puts("Beware: this test set local variable loop_i and it cannot be unset!");
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* Reset local variable. */
+		ut_assertok(run_command("loop_i=", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		puts("Beware: this test set local variable loop_i and it cannot be unset!");
+	}
 
 	return 0;
 }
@@ -31,12 +39,30 @@ static int hush_test_while(struct unit_test_state *uts)
 {
 	console_record_reset_enable();
 
-	/* Exit status is that of test, so 1 since test is false to quit the loop. */
-	ut_asserteq(1, run_command("while test -z \"$loop_foo\"; do echo bar; loop_foo=quux; done", 0));
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/*
+		 * Hush 2021 always returns 0 from while loop...
+		 * You can see code snippet near this line to have a better
+		 * understanding:
+		 * debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
+		 */
+		ut_assertok(run_command("while test -z \"$loop_foo\"; do echo bar; loop_foo=quux; done", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		/*
+		 * Exit status is that of test, so 1 since test is false to quit
+		 * the loop.
+		 */
+		ut_asserteq(1, run_command("while test -z \"$loop_foo\"; do echo bar; loop_foo=quux; done", 0));
+	}
 	ut_assert_nextline("bar");
 	ut_assert_console_end();
 
-	puts("Beware: this test set local variable loop_foo and it cannot be unset!");
+	if (gd->flags & GD_FLG_HUSH_2021_PARSER) {
+		/* Reset local variable. */
+		ut_assertok(run_command("loop_foo=", 0));
+	} else if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+		puts("Beware: this test set local variable loop_foo and it cannot be unset!");
+	}
 
 	return 0;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 22/23] cli: hush_2021: Add upstream commits up to 30th January 2023.
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (20 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021 Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 19:47 ` [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy Francis Laniel
  22 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Samuel Dionne-Riel, Linus Walleij,
	Alexey Romanov, Fabrice Gasnier, Marek Vasut, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Rui Miguel Silva,
	Andrew Scull

This commit adds the following hush busybox upstream commits:
93ae7464e6e4 ("hush: restore SIGHUP handling, this time explain why we do what we do")
1fdb33bd07e5 ("hush: restore tty pgrp on SIGHUP")
6101b6d3eaa0 ("hush: remove special handling of SIGHUP")
93e0898c663a ("shell: fix SIGWINCH and SIGCHLD (in hush) interrupting line input, closes 15256")
969e00816835 ("hush: code shrink")
27be0e8cfeb6 ("shell: fix compile failures in some configs")
7d1c7d833785 ("ash,hush: use HOME for tab completion and prompts")
21afddefd258 ("hush: fix "error: invalid preprocessing directive ##"")
e53c7dbafc78 ("hush: fix set -n to act immediately, not just after run_list()
")
574b9c446da1 ("hush: fix var_LINENO3.tests failure")
49bcf9f40cff ("hush: speed up ${x//\*/|} too")
53b2fdcdba4c ("*: add NOINLINEs where code noticeably shrinks")
7c3e96d4b3d4 ("shell: use more compact SHELL_ASH / HUSH config defines. no code changes")
62f1eed1e191 ("hush: in a comment, document what -i might be doing")
aaf3d5ba74c5 ("shell: tweak --help")
db5546ca1018 ("libbb: code shrink: introduce and use [_]exit_SUCCESS()")
931c55f9e2b4 ("libbb: invert the meaning of SETUP_ENV_NO_CHDIR -> SETUP_ENV_CHDIR")
12566e7f9b5e ("ash,hush: fix handling of SIGINT while waiting for interactive input")
987be932ed3c ("*: slap on a few ALIGN_PTR where appropriate")

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
---
 common/cli_hush_2021.c     |   2 +-
 common/cli_hush_upstream.c | 224 +++++++++++++++++++++++++++----------
 2 files changed, 164 insertions(+), 62 deletions(-)

diff --git a/common/cli_hush_2021.c b/common/cli_hush_2021.c
index 0a207d147b..4c46176cb2 100644
--- a/common/cli_hush_2021.c
+++ b/common/cli_hush_2021.c
@@ -26,7 +26,7 @@
 /*
  * BusyBox Version: UPDATE THIS WHEN PULLING NEW UPSTREAM REVISION!
  */
-#define BB_VER			"1.34.0.git37460f5daff9"
+#define BB_VER			"1.35.0.git7d1c7d833785"
 
 /*
  * Define hush features by the names used upstream.
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index 23392939e1..93796e87c5 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -339,7 +339,7 @@
  * therefore we don't show them either.
  */
 //usage:#define hush_trivial_usage
-//usage:	"[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE [ARGS] | -s [ARGS]]"
+//usage:	"[-enxl] [-c 'SCRIPT' [ARG0 ARGS] | FILE ARGS | -s ARGS]"
 //usage:#define hush_full_usage "\n\n"
 //usage:	"Unix shell interpreter"
 
@@ -374,7 +374,7 @@
 # define F_DUPFD_CLOEXEC F_DUPFD
 #endif
 
-#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !(ENABLE_ASH || ENABLE_SH_IS_ASH || ENABLE_BASH_IS_ASH)
+#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !ENABLE_SHELL_ASH
 # include "embedded_scripts.h"
 #else
 # define NUM_SCRIPTS 0
@@ -574,7 +574,7 @@ enum {
 #define NULL_O_STRING { NULL }
 
 #ifndef debug_printf_parse
-static const char *const assignment_flag[] = {
+static const char *const assignment_flag[] ALIGN_PTR = {
 	"MAYBE_ASSIGNMENT",
 	"DEFINITELY_ASSIGNMENT",
 	"NOT_ASSIGNMENT",
@@ -958,6 +958,7 @@ struct globals {
 #if ENABLE_HUSH_INTERACTIVE
 	smallint promptmode; /* 0: PS1, 1: PS2 */
 #endif
+	/* set by signal handler if SIGINT is received _and_ its trap is not set */
 	smallint flag_SIGINT;
 #ifndef __U_BOOT__
 #if ENABLE_HUSH_LOOPS
@@ -1918,7 +1919,13 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * SIGQUIT: ignore
  * SIGTERM (interactive): ignore
  * SIGHUP (interactive):
- *    send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit
+ *    Send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit.
+ *    Kernel would do this for us ("orphaned process group" handling
+ *    according to POSIX) if we are a session leader and thus our death
+ *    frees the controlling tty, but to be bash-compatible, we also do it
+ *    for every interactive shell's death by SIGHUP.
+ *    (Also, we need to restore tty pgrp, otherwise e.g. Midnight Commander
+ *    backgrounds when hush started from it gets killed by SIGHUP).
  * SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore
  *    Note that ^Z is handled not by trapping SIGTSTP, but by seeing
  *    that all pipe members are stopped. Try this in bash:
@@ -2034,6 +2041,14 @@ enum {
 static void record_pending_signo(int sig)
 {
 	sigaddset(&G.pending_set, sig);
+#if ENABLE_FEATURE_EDITING
+	if (sig != SIGCHLD
+	 || (G_traps && G_traps[SIGCHLD] && G_traps[SIGCHLD][0])
+	 /* ^^^ if SIGCHLD, interrupt line reading only if it has a trap */
+	) {
+		bb_got_signal = sig; /* for read_line_input: "we got a signal" */
+	}
+#endif
 #if ENABLE_HUSH_FAST
 	if (sig == SIGCHLD) {
 		G.count_SIGCHLD++;
@@ -2265,20 +2280,27 @@ static int check_and_run_traps(void)
 			break;
 #if ENABLE_HUSH_JOB
 		case SIGHUP: {
-//TODO: why are we doing this? ash and dash don't do this,
-//they have no handler for SIGHUP at all,
-//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
-			struct pipe *job;
-			debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
-			/* bash is observed to signal whole process groups,
-			 * not individual processes */
-			for (job = G.job_list; job; job = job->next) {
-				if (job->pgrp <= 0)
-					continue;
-				debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
-				if (kill(- job->pgrp, SIGHUP) == 0)
-					kill(- job->pgrp, SIGCONT);
+			/* if (G_interactive_fd) - no need to check, the handler
+			 * is only installed if we *are* interactive */
+			{
+				/* bash compat: "Before exiting, an interactive
+				 * shell resends the SIGHUP to all jobs, running
+				 * or stopped.  Stopped jobs are sent SIGCONT
+				 * to ensure that they receive the SIGHUP."
+				 */
+				struct pipe *job;
+				debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
+				/* bash is observed to signal whole process groups,
+				 * not individual processes */
+				for (job = G.job_list; job; job = job->next) {
+					if (job->pgrp <= 0)
+						continue;
+					debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+					if (kill(- job->pgrp, SIGHUP) == 0)
+						kill(- job->pgrp, SIGCONT);
+				}
 			}
+			/* this restores tty pgrp, then kills us with SIGHUP */
 			sigexit(SIGHUP);
 		}
 #endif
@@ -2581,10 +2603,15 @@ int set_local_var_2021(char *str, int flags)
 }
 
 #ifndef __U_BOOT__
+static int set_local_var0(char *str)
+{
+	return set_local_var(str, 0);
+}
+
 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, /*flag:*/ 0);
+	set_local_var0(var);
 }
 
 /* Used at startup and after each cd */
@@ -2788,30 +2815,54 @@ static void get_user_input(struct in_str *i)
 	for (;;) {
 		reinit_unicode_for_hush();
 		G.flag_SIGINT = 0;
-		/* buglet: SIGINT will not make new prompt to appear _at once_,
-		 * only after <Enter>. (^C works immediately) */
-		r = read_line_input(G.line_input_state, prompt_str,
+
+		bb_got_signal = 0;
+		if (!sigisemptyset(&G.pending_set)) {
+			/* Whoops, already got a signal, do not call read_line_input */
+			bb_got_signal = r = -1;
+		} else {
+			/* For shell, LI_INTERRUPTIBLE is set:
+			 * read_line_input will abort on either
+			 * getting EINTR in poll() and bb_got_signal became != 0,
+			 * or if it sees bb_got_signal != 0
+			 * (IOW: if signal arrives before poll() is reached).
+			 * Interactive testcases:
+			 * (while kill -INT $$; do sleep 1; done) &
+			 * #^^^ prints ^C, prints prompt, repeats
+			 * trap 'echo I' int; (while kill -INT $$; do sleep 1; done) &
+			 * #^^^ prints ^C, prints "I", prints prompt, repeats
+			 * trap 'echo T' term; (while kill $$; do sleep 1; done) &
+			 * #^^^ prints "T", prints prompt, repeats
+			 * #(bash 5.0.17 exits after first "T", looks like a bug)
+			 */
+			r = read_line_input(G.line_input_state, prompt_str,
 				G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
-		);
-		/* read_line_input intercepts ^C, "convert" it to SIGINT */
-		if (r == 0) {
-			raise(SIGINT);
+			);
+			/* read_line_input intercepts ^C, "convert" it to SIGINT */
+			if (r == 0)
+				raise(SIGINT);
+		}
+		/* bash prints ^C (before running a trap, if any)
+		 * both on keyboard ^C and on real SIGINT (non-kbd generated).
+		 */
+		if (sigismember(&G.pending_set, SIGINT)) {
+			write(STDOUT_FILENO, "^C\n", 3);
+			G.last_exitcode = 128 | SIGINT;
 		}
 		check_and_run_traps();
-		if (r != 0 && !G.flag_SIGINT)
+		if (r == 0) /* keyboard ^C? */
+			continue; /* go back, read another input line */
+		if (r > 0) /* normal input? (no ^C, no ^D, no signals) */
 			break;
-		/* ^C or SIGINT: repeat */
-		/* bash prints ^C even on real SIGINT (non-kbd generated) */
-		write(STDOUT_FILENO, "^C\n", 3);
-		G.last_exitcode = 128 | SIGINT;
-	}
-	if (r < 0) {
-		/* EOF/error detected */
-		/* ^D on interactive input goes to next line before exiting: */
-		write(STDOUT_FILENO, "\n", 1);
-		i->p = NULL;
-		i->peek_buf[0] = r = EOF;
-		return r;
+		if (!bb_got_signal) {
+			/* r < 0: ^D/EOF/error detected (but not signal) */
+			/* ^D on interactive input goes to next line before exiting: */
+			write(STDOUT_FILENO, "\n", 1);
+			i->p = NULL;
+			i->peek_buf[0] = r = EOF;
+			return r;
+		}
+		/* it was a signal: go back, read another input line */
 	}
 	i->p = G.user_input_buf;
 	return (unsigned char)*i->p++;
@@ -2991,6 +3042,12 @@ static int i_getch(struct in_str *i)
 		if (ch != '\0') {
 			i->p++;
 			i->last_char = ch;
+#if ENABLE_HUSH_LINENO_VAR
+			if (ch == '\n') {
+				G.parse_lineno++;
+				debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno);
+			}
+#endif
 			return ch;
 		}
 		return EOF;
@@ -3638,7 +3695,7 @@ static int glob_brace(char *pattern, o_string *o, int n)
 	 * NEXT points past the terminator of the first element, and REST
 	 * points past the final }.  We will accumulate result names from
 	 * recursive runs for each brace alternative in the buffer using
-	 * GLOB_APPEND.  */
+	 * GLOB_APPEND. */
 
 	p = begin + 1;
 	while (1) {
@@ -3954,7 +4011,7 @@ static void free_pipe_list(struct pipe *pi)
 #ifndef debug_print_tree
 static void debug_print_tree(struct pipe *pi, int lvl)
 {
-	static const char *const PIPE[] = {
+	static const char *const PIPE[] ALIGN_PTR = {
 		[PIPE_SEQ] = "SEQ",
 		[PIPE_AND] = "AND",
 		[PIPE_OR ] = "OR" ,
@@ -3989,7 +4046,7 @@ static void debug_print_tree(struct pipe *pi, int lvl)
 		[RES_XXXX ] = "XXXX" ,
 		[RES_SNTX ] = "SNTX" ,
 	};
-	static const char *const CMDTYPE[] = {
+	static const char *const CMDTYPE[] ALIGN_PTR = {
 		"{}",
 		"()",
 		"[noglob]",
@@ -5354,7 +5411,7 @@ static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_st
 # undef as_string
 }
 #else
-# #define parse_dollar_squote(as_string, dest, input) 0
+# define parse_dollar_squote(as_string, dest, input) 0
 #endif /* BASH_DOLLAR_SQUOTE */
 #endif /* !__U_BOOT__ */
 
@@ -6721,7 +6778,7 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
 
 /* Expanding ARG in ${var+ARG}, ${var-ARG}
  */
-static int encode_then_append_var_plusminus(o_string *output, int n,
+static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n,
 		char *str, int dquoted)
 {
 	struct in_str input;
@@ -6886,16 +6943,21 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 /* ${var/[/]pattern[/repl]} helpers */
 static char *strstr_pattern(char *val, const char *pattern, int *size)
 {
-	int sz = strcspn(pattern, "*?[\\");
-	if (pattern[sz] == '\0') {
+	int first_escaped = (pattern[0] == '\\' && pattern[1]);
+	/* "first_escaped" trick allows to treat e.g. "\*no_glob_chars"
+	 * as literal too (as it is semi-common, and easy to accomodate
+	 * by just using str + 1).
+	 */
+	int sz = strcspn(pattern + first_escaped * 2, "*?[\\");
+	if ((pattern + first_escaped * 2)[sz] == '\0') {
 		/* Optimization for trivial patterns.
 		 * Testcase for very slow replace (performs about 22k replaces):
 		 * x=::::::::::::::::::::::
 		 * x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;x=$x$x;echo ${#x}
 		 * echo "${x//:/|}"
 		 */
-		*size = sz;
-		return strstr(val, pattern);
+		*size = sz + first_escaped;
+		return strstr(val, pattern + first_escaped);
 	}
 
 	while (1) {
@@ -7371,7 +7433,7 @@ static NOINLINE int expand_one_var(o_string *output, int n,
 							val = NULL;
 						} else {
 							char *new_var = xasprintf("%s=%s", var, val);
-							set_local_var(new_var, /*flag:*/ 0);
+							set_local_var0(new_var);
 						}
 					}
 				}
@@ -8026,7 +8088,11 @@ static int parse_and_run_string(const char *s)
 #endif /* __U_BOOT__ */
 {
 	struct in_str input;
+#ifndef __U_BOOT__
+	IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#else /* __U_BOOT__ */
 	//IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#endif /* __U_BOOT__ */
 
 	setup_string_in_str(&input, s);
 #ifndef __U_BOOT__
@@ -8034,7 +8100,11 @@ static int parse_and_run_string(const char *s)
 #else /* __U_BOOT__ */
 	return parse_and_run_stream(&input, '\0');
 #endif /* __U_BOOT__ */
-	//IF_HUSH_LINENO_VAR(G.parse_lineno = sv;)
+#ifndef __U_BOOT__
+	IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#else /* __U_BOOT__ */
+	//IF_HUSH_LINENO_VAR(unsigned sv = G.parse_lineno;)
+#endif /* __U_BOOT__ */
 }
 
 #ifdef __U_BOOT__
@@ -8142,7 +8212,7 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p)
 		if (is_prefixed_with(s, "trap")
 		 && skip_whitespace(s + 4)[0] == '\0'
 		) {
-			static const char *const argv[] = { NULL, NULL };
+			static const char *const argv[] ALIGN_PTR = { NULL, NULL };
 			builtin_trap((char**)argv);
 			fflush_all(); /* important */
 			_exit(0);
@@ -8671,7 +8741,7 @@ static const struct built_in_command *find_builtin(const char *name)
 	return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]);
 }
 
-#if ENABLE_HUSH_JOB && EDITING_HAS_get_exe_name
+#if ENABLE_HUSH_JOB && ENABLE_FEATURE_TAB_COMPLETION
 static const char * FAST_FUNC get_builtin_name(int i)
 {
 	if (/*i >= 0 && */ i < ARRAY_SIZE(bltins1)) {
@@ -9107,7 +9177,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 		 * expand_assignments(): think about ... | var=`sleep 1` | ...
 		 */
 		free_strings(new_env);
-		_exit(EXIT_SUCCESS);
+		_exit_SUCCESS();
 	}
 
 	sv_shadowed = G.shadowed_vars_pp;
@@ -9288,7 +9358,7 @@ static void pseudo_exec(nommu_save_t *nommu_save,
 
 	/* Case when we are here: ... | >file */
 	debug_printf_exec("pseudo_exec'ed null command\n");
-	_exit(EXIT_SUCCESS);
+	_exit_SUCCESS();
 }
 
 #if ENABLE_HUSH_JOB
@@ -9895,7 +9965,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 #endif
 				debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
 #ifndef __U_BOOT__
-				if (set_local_var(p, /*flag:*/ 0)) {
+				if (set_local_var0(p)) {
 #else /* __U_BOOT__ */
 				if (set_local_var_2021(p, /*flag:*/ 0)) {
 #endif
@@ -10421,7 +10491,7 @@ static int run_list(struct pipe *pi)
 				static const char encoded_dollar_at[] ALIGN1 = {
 					SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0'
 				}; /* encoded representation of "$@" */
-				static const char *const encoded_dollar_at_argv[] = {
+				static const char *const encoded_dollar_at_argv[] ALIGN_PTR = {
 					encoded_dollar_at, NULL
 				}; /* argv list with one element: "$@" */
 				char **vals;
@@ -10452,7 +10522,7 @@ static int run_list(struct pipe *pi)
 			/* Insert next value from for_lcur */
 			/* note: *for_lcur already has quotes removed, $var expanded, etc */
 #ifndef __U_BOOT__
-			set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*flag:*/ 0);
+			set_local_var_from_halves(pi->cmds[0].argv[0], *for_lcur++);
 #else /* __U_BOOT__ */
 			/* We cannot use xasprintf, so we emulate it. */
 			char *full_var;
@@ -10548,8 +10618,11 @@ static int run_list(struct pipe *pi)
 		G.flag_break_continue = 0;
 #endif
 #endif /* !__U_BOOT__ */
+#ifndef __U_BOOT__
+		rcode = r = G.o_opt[OPT_O_NOEXEC] ? 0 : run_pipe(pi);
+		/* NB: rcode is a smalluint, r is int */
+#else /* __U_BOOT__ */
 		rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
-#ifdef __U_BOOT__
 		if (r <= EXIT_RET_CODE) {
 			int previous_rcode = G.last_exitcode;
 			/*
@@ -10839,7 +10912,10 @@ static int set_mode(int state, char mode, const char *o_opt)
 	int idx;
 	switch (mode) {
 	case 'n':
-		G.o_opt[OPT_O_NOEXEC] = state;
+		/* set -n has no effect in interactive shell */
+		/* Try: while set -n; do echo $-; done */
+		if (!G_interactive_fd)
+			G.o_opt[OPT_O_NOEXEC] = state;
 		break;
 	case 'x':
 		IF_HUSH_MODE_X(G_x_mode = state;)
@@ -10912,7 +10988,7 @@ int hush_main(int argc, char **argv)
 
 	cached_getpid = getpid();   /* for tcsetpgrp() during init */
 	G.root_pid = cached_getpid; /* for $PID  (NOMMU can override via -$HEXPID:HEXPPID:...) */
-	G.root_ppid = getppid();    /* for $PPID (NOMMU can override)  */
+	G.root_ppid = getppid();    /* for $PPID (NOMMU can override) */
 
 	/* Deal with HUSH_VERSION */
 	debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
@@ -11043,6 +11119,29 @@ int hush_main(int argc, char **argv)
 			/* Well, we cannot just declare interactiveness,
 			 * we have to have some stuff (ctty, etc) */
 			/* G_interactive_fd++; */
+//There are a few cases where bash -i -c 'SCRIPT'
+//has visible effect (differs from bash -c 'SCRIPT'):
+//it ignores TERM:
+//	bash -i -c 'kill $$; echo ALIVE'
+//	ALIVE
+//it resets SIG_IGNed HUP to SIG_DFL:
+//	trap '' hup; bash -i -c 'kill -hup $$; echo ALIVE'
+//	Hangup   [the message is not printed by bash, it's the shell which started it]
+//is talkative about jobs and exiting:
+//	bash -i -c 'sleep 1 & exit'
+//	[1] 16170
+//	exit
+//includes $ENV file (only if run as "sh"):
+//	echo last >/tmp/ENV; ENV=/tmp/ENV sh -i -c 'echo HERE'
+//	last: cannot open /var/log/wtmp: No such file or directory
+//	HERE
+//(under "bash", it's the opposite: it runs $BASH_ENV file only *without* -i).
+//
+//ash -i -c 'sleep 3; sleep 3', on ^C, drops into a prompt instead of exiting
+//(this may be a bug, bash does not do this).
+//(ash -i -c 'sleep 3' won't show this, the last command gets auto-"exec"ed)
+//
+//None of the above feel like useful features people would rely on.
 			break;
 		case 's':
 			G.opt_s = 1;
@@ -11307,9 +11406,12 @@ int hush_main(int argc, char **argv)
 
 # if ENABLE_FEATURE_EDITING
 		G.line_input_state = new_line_input_t(FOR_SHELL);
-#  if EDITING_HAS_get_exe_name
+#  if ENABLE_FEATURE_TAB_COMPLETION
 		G.line_input_state->get_exe_name = get_builtin_name;
 #  endif
+#  if EDITING_HAS_sh_get_var
+		G.line_input_state->sh_get_var = get_local_var_value;
+#  endif
 # endif
 # if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
 		{
@@ -12422,7 +12524,7 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
 	/* TODO: bash prints a string representation
 	 * of job being foregrounded (like "sleep 1 | cat") */
 	if (argv[0][0] == 'f' && G_saved_tty_pgrp) {
-		/* Put the job into the foreground.  */
+		/* Put the job into the foreground. */
 		tcsetpgrp(G_interactive_fd, pi->pgrp);
 	}
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy
  2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
                   ` (21 preceding siblings ...)
  2023-03-30 19:47 ` [RFC PATCH v7 22/23] cli: hush_2021: Add upstream commits up to 30th January 2023 Francis Laniel
@ 2023-03-30 19:47 ` Francis Laniel
  2023-03-30 20:17   ` Tony Dinh
  22 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 19:47 UTC (permalink / raw)
  To: u-boot
  Cc: Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Francis Laniel, Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios, Marek Vasut,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

This commit set CONFIG_HUSH_PARSER_2021 as the default to trigger the CI with
this parser.

Nonetheless, the keymile (i.e. VENDOR_KM) board family is not compatible with
new 2021 hush parser.
Indeed, This boards used set_local_var() to store some variables as local shell.
They then used get_local_var() to retrieve the variables values.
Sadly, this two functions do not exist with CONFIG_HUSH_PARSER_2021.
A patch was proposed to use environment variables rather than local variables
but it does not tackle the problem, so complementary work is needed to make
this boards use CONFIG_HUSH_PARSER_2021 [1].

We also remove a #undef of CONFIG_FEATURE_SH_STANDALONE as it does not exist in
U-Boot and causes troubles in the CI.

We also set CONFIG_LTO for kirkwoord sheevaplug and phytec bk4r1, otherwise it
hits its board size limit.
By enabling this option, we also had to add assembly for __gnu_thumb1_case_si.
It was taken from upstream gcc and adapted as width suffix was removed for the
add [2].

Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
[1] https://marc.info/?l=u-boot&m=165541917618725&w=2
[2] https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be9018f1611/libgcc/config/arm/lib1funcs.S#L2156
---
 arch/arm/lib/lib1funcs.S     | 17 +++++++++++++++++
 cmd/Kconfig                  |  3 ++-
 common/cli_hush_upstream.c   |  1 -
 configs/bk4r1_defconfig      |  1 +
 configs/sheevaplug_defconfig |  1 +
 5 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S
index de15d09e36..a1f44d9454 100644
--- a/arch/arm/lib/lib1funcs.S
+++ b/arch/arm/lib/lib1funcs.S
@@ -419,4 +419,21 @@ ENTRY(__gnu_thumb1_case_uhi)
 	ret	lr
 ENDPROC(__gnu_thumb1_case_uhi)
 .popsection
+
+/* Taken and adapted from: https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be9018f1611/libgcc/config/arm/lib1funcs.S#L2156 */
+.pushsection .text.__gnu_thumb1_case_si, "ax"
+ENTRY(__gnu_thumb1_case_si)
+	push	{r0, r1}
+	mov	r1, lr
+	adds	r1, r1, #2	/* Align to word.  */
+	lsrs	r1, r1, #2
+	lsls	r0, r0, #2
+	lsls	r1, r1, #2
+	ldr	r0, [r1, r0]
+	adds	r0, r0, r1
+	mov	lr, r0
+	pop	{r0, r1}
+	mov	pc, lr		/* We know we were called from thumb code.  */
+ENDPROC(__gnu_thumb1_case_si)
+.popsection
 #endif
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 1b9d04680d..d4809fcfa4 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -28,7 +28,7 @@ menu "Hush flavor to use"
 
 	config HUSH_OLD_PARSER
 		bool "Use hush old parser"
-		default y
+		default y if VENDOR_KM
 		help
 		  This option enables the old flavor of hush based on hush Busybox from
 		  2005.
@@ -37,6 +37,7 @@ menu "Hush flavor to use"
 
 	config HUSH_2021_PARSER
 		bool "Use hush 2021 parser"
+		default y if !VENDOR_KM
 		help
 		  This option enables the new flavor of hush based on hush Busybox from
 		  2021.
diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
index 93796e87c5..78a13eeb62 100644
--- a/common/cli_hush_upstream.c
+++ b/common/cli_hush_upstream.c
@@ -427,7 +427,6 @@
 #include "NUM_APPLETS.h"
 #if NUM_APPLETS == 1
 /* STANDALONE does not make sense, and won't compile */
-# undef CONFIG_FEATURE_SH_STANDALONE
 # undef ENABLE_FEATURE_SH_STANDALONE
 # undef IF_FEATURE_SH_STANDALONE
 # undef IF_NOT_FEATURE_SH_STANDALONE
diff --git a/configs/bk4r1_defconfig b/configs/bk4r1_defconfig
index 66adeac725..95f0c30cde 100644
--- a/configs/bk4r1_defconfig
+++ b/configs/bk4r1_defconfig
@@ -18,6 +18,7 @@ CONFIG_TARGET_BK4R1=y
 CONFIG_SYS_LOAD_ADDR=0x82000000
 CONFIG_SYS_MEMTEST_START=0x80010000
 CONFIG_SYS_MEMTEST_END=0x87c00000
+CONFIG_LTO=y
 CONFIG_HAS_BOARD_SIZE_LIMIT=y
 CONFIG_BOARD_SIZE_LIMIT=520192
 CONFIG_FIT=y
diff --git a/configs/sheevaplug_defconfig b/configs/sheevaplug_defconfig
index 2e4901b840..365f779cc8 100644
--- a/configs/sheevaplug_defconfig
+++ b/configs/sheevaplug_defconfig
@@ -16,6 +16,7 @@ CONFIG_ENV_OFFSET=0x80000
 CONFIG_DEFAULT_DEVICE_TREE="kirkwood-sheevaplug"
 CONFIG_IDENT_STRING="\nMarvell-Sheevaplug"
 CONFIG_SYS_LOAD_ADDR=0x800000
+CONFIG_LTO=y
 CONFIG_HAS_BOARD_SIZE_LIMIT=y
 CONFIG_BOARD_SIZE_LIMIT=524288
 CONFIG_BOOTDELAY=3
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy
  2023-03-30 19:47 ` [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy Francis Laniel
@ 2023-03-30 20:17   ` Tony Dinh
  2023-03-30 22:14     ` Francis Laniel
  0 siblings, 1 reply; 37+ messages in thread
From: Tony Dinh @ 2023-03-30 20:17 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Albert ARIBAUD (3ADEV),
	Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios, Marek Vasut,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

Hi Francis,

On Thu, Mar 30, 2023 at 12:50 PM Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> This commit set CONFIG_HUSH_PARSER_2021 as the default to trigger the CI with
> this parser.
>
> Nonetheless, the keymile (i.e. VENDOR_KM) board family is not compatible with
> new 2021 hush parser.
> Indeed, This boards used set_local_var() to store some variables as local shell.
> They then used get_local_var() to retrieve the variables values.
> Sadly, this two functions do not exist with CONFIG_HUSH_PARSER_2021.
> A patch was proposed to use environment variables rather than local variables
> but it does not tackle the problem, so complementary work is needed to make
> this boards use CONFIG_HUSH_PARSER_2021 [1].
>
> We also remove a #undef of CONFIG_FEATURE_SH_STANDALONE as it does not exist in
> U-Boot and causes troubles in the CI.
>
> We also set CONFIG_LTO for kirkwoord sheevaplug and phytec bk4r1, otherwise it
> hits its board size limit.
> By enabling this option, we also had to add assembly for __gnu_thumb1_case_si.
> It was taken from upstream gcc and adapted as width suffix was removed for the
> add [2].

Thanks for adding __gnu_thumb1_case_si ! It will help not just the
Sheevaplug board, but also a few other boards too. I'll give this a
try.

All the best,
Tony

>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> [1] https://marc.info/?l=u-boot&m=165541917618725&w=2
> [2] https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be9018f1611/libgcc/config/arm/lib1funcs.S#L2156
> ---
>  arch/arm/lib/lib1funcs.S     | 17 +++++++++++++++++
>  cmd/Kconfig                  |  3 ++-
>  common/cli_hush_upstream.c   |  1 -
>  configs/bk4r1_defconfig      |  1 +
>  configs/sheevaplug_defconfig |  1 +
>  5 files changed, 21 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S
> index de15d09e36..a1f44d9454 100644
> --- a/arch/arm/lib/lib1funcs.S
> +++ b/arch/arm/lib/lib1funcs.S
> @@ -419,4 +419,21 @@ ENTRY(__gnu_thumb1_case_uhi)
>         ret     lr
>  ENDPROC(__gnu_thumb1_case_uhi)
>  .popsection
> +
> +/* Taken and adapted from: https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be9018f1611/libgcc/config/arm/lib1funcs.S#L2156 */
> +.pushsection .text.__gnu_thumb1_case_si, "ax"
> +ENTRY(__gnu_thumb1_case_si)
> +       push    {r0, r1}
> +       mov     r1, lr
> +       adds    r1, r1, #2      /* Align to word.  */
> +       lsrs    r1, r1, #2
> +       lsls    r0, r0, #2
> +       lsls    r1, r1, #2
> +       ldr     r0, [r1, r0]
> +       adds    r0, r0, r1
> +       mov     lr, r0
> +       pop     {r0, r1}
> +       mov     pc, lr          /* We know we were called from thumb code.  */
> +ENDPROC(__gnu_thumb1_case_si)
> +.popsection
>  #endif
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 1b9d04680d..d4809fcfa4 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -28,7 +28,7 @@ menu "Hush flavor to use"
>
>         config HUSH_OLD_PARSER
>                 bool "Use hush old parser"
> -               default y
> +               default y if VENDOR_KM
>                 help
>                   This option enables the old flavor of hush based on hush Busybox from
>                   2005.
> @@ -37,6 +37,7 @@ menu "Hush flavor to use"
>
>         config HUSH_2021_PARSER
>                 bool "Use hush 2021 parser"
> +               default y if !VENDOR_KM
>                 help
>                   This option enables the new flavor of hush based on hush Busybox from
>                   2021.
> diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
> index 93796e87c5..78a13eeb62 100644
> --- a/common/cli_hush_upstream.c
> +++ b/common/cli_hush_upstream.c
> @@ -427,7 +427,6 @@
>  #include "NUM_APPLETS.h"
>  #if NUM_APPLETS == 1
>  /* STANDALONE does not make sense, and won't compile */
> -# undef CONFIG_FEATURE_SH_STANDALONE
>  # undef ENABLE_FEATURE_SH_STANDALONE
>  # undef IF_FEATURE_SH_STANDALONE
>  # undef IF_NOT_FEATURE_SH_STANDALONE
> diff --git a/configs/bk4r1_defconfig b/configs/bk4r1_defconfig
> index 66adeac725..95f0c30cde 100644
> --- a/configs/bk4r1_defconfig
> +++ b/configs/bk4r1_defconfig
> @@ -18,6 +18,7 @@ CONFIG_TARGET_BK4R1=y
>  CONFIG_SYS_LOAD_ADDR=0x82000000
>  CONFIG_SYS_MEMTEST_START=0x80010000
>  CONFIG_SYS_MEMTEST_END=0x87c00000
> +CONFIG_LTO=y
>  CONFIG_HAS_BOARD_SIZE_LIMIT=y
>  CONFIG_BOARD_SIZE_LIMIT=520192
>  CONFIG_FIT=y
> diff --git a/configs/sheevaplug_defconfig b/configs/sheevaplug_defconfig
> index 2e4901b840..365f779cc8 100644
> --- a/configs/sheevaplug_defconfig
> +++ b/configs/sheevaplug_defconfig
> @@ -16,6 +16,7 @@ CONFIG_ENV_OFFSET=0x80000
>  CONFIG_DEFAULT_DEVICE_TREE="kirkwood-sheevaplug"
>  CONFIG_IDENT_STRING="\nMarvell-Sheevaplug"
>  CONFIG_SYS_LOAD_ADDR=0x800000
> +CONFIG_LTO=y
>  CONFIG_HAS_BOARD_SIZE_LIMIT=y
>  CONFIG_BOARD_SIZE_LIMIT=524288
>  CONFIG_BOOTDELAY=3
> --
> 2.34.1
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy
  2023-03-30 20:17   ` Tony Dinh
@ 2023-03-30 22:14     ` Francis Laniel
  2023-03-31 23:17       ` Tony Dinh
  0 siblings, 1 reply; 37+ messages in thread
From: Francis Laniel @ 2023-03-30 22:14 UTC (permalink / raw)
  To: Tony Dinh
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Simon Glass,
	Harald Seiler, Albert ARIBAUD (3ADEV),
	Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios, Marek Vasut,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

Hi.

Le jeudi 30 mars 2023, 21:17:33 WEST Tony Dinh a écrit :
> Hi Francis,
> 
> On Thu, Mar 30, 2023 at 12:50 PM Francis Laniel
> 
> <francis.laniel@amarulasolutions.com> wrote:
> > This commit set CONFIG_HUSH_PARSER_2021 as the default to trigger the CI
> > with this parser.
> > 
> > Nonetheless, the keymile (i.e. VENDOR_KM) board family is not compatible
> > with new 2021 hush parser.
> > Indeed, This boards used set_local_var() to store some variables as local
> > shell. They then used get_local_var() to retrieve the variables values.
> > Sadly, this two functions do not exist with CONFIG_HUSH_PARSER_2021.
> > A patch was proposed to use environment variables rather than local
> > variables but it does not tackle the problem, so complementary work is
> > needed to make this boards use CONFIG_HUSH_PARSER_2021 [1].
> > 
> > We also remove a #undef of CONFIG_FEATURE_SH_STANDALONE as it does not
> > exist in U-Boot and causes troubles in the CI.
> > 
> > We also set CONFIG_LTO for kirkwoord sheevaplug and phytec bk4r1,
> > otherwise it hits its board size limit.
> > By enabling this option, we also had to add assembly for
> > __gnu_thumb1_case_si. It was taken from upstream gcc and adapted as width
> > suffix was removed for the add [2].
> 
> Thanks for adding __gnu_thumb1_case_si ! It will help not just the
> Sheevaplug board, but also a few other boards too. I'll give this a
> try.

You are welcome!
I nonetheless cannot ensure the modifications I brought to the code are correct 
as I do not have the board to test them.
So, if you can test it on a board it will be really welcomed and in case you 
find any problem I will add your suggested changes to the commit!

> 
> All the best,
> Tony
> 
> > Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> > [1] https://marc.info/?l=u-boot&m=165541917618725&w=2
> > [2]
> > https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be901
> > 8f1611/libgcc/config/arm/lib1funcs.S#L2156 ---
> > 
> >  arch/arm/lib/lib1funcs.S     | 17 +++++++++++++++++
> >  cmd/Kconfig                  |  3 ++-
> >  common/cli_hush_upstream.c   |  1 -
> >  configs/bk4r1_defconfig      |  1 +
> >  configs/sheevaplug_defconfig |  1 +
> >  5 files changed, 21 insertions(+), 2 deletions(-)
> > 
> > diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S
> > index de15d09e36..a1f44d9454 100644
> > --- a/arch/arm/lib/lib1funcs.S
> > +++ b/arch/arm/lib/lib1funcs.S
> > @@ -419,4 +419,21 @@ ENTRY(__gnu_thumb1_case_uhi)
> > 
> >         ret     lr
> >  
> >  ENDPROC(__gnu_thumb1_case_uhi)
> >  .popsection
> > 
> > +
> > +/* Taken and adapted from:
> > https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be901
> > 8f1611/libgcc/config/arm/lib1funcs.S#L2156 */ +.pushsection
> > .text.__gnu_thumb1_case_si, "ax"
> > +ENTRY(__gnu_thumb1_case_si)
> > +       push    {r0, r1}
> > +       mov     r1, lr
> > +       adds    r1, r1, #2      /* Align to word.  */
> > +       lsrs    r1, r1, #2
> > +       lsls    r0, r0, #2
> > +       lsls    r1, r1, #2
> > +       ldr     r0, [r1, r0]
> > +       adds    r0, r0, r1
> > +       mov     lr, r0
> > +       pop     {r0, r1}
> > +       mov     pc, lr          /* We know we were called from thumb code.
> >  */ +ENDPROC(__gnu_thumb1_case_si)
> > +.popsection
> > 
> >  #endif
> > 
> > diff --git a/cmd/Kconfig b/cmd/Kconfig
> > index 1b9d04680d..d4809fcfa4 100644
> > --- a/cmd/Kconfig
> > +++ b/cmd/Kconfig
> > @@ -28,7 +28,7 @@ menu "Hush flavor to use"
> > 
> >         config HUSH_OLD_PARSER
> >         
> >                 bool "Use hush old parser"
> > 
> > -               default y
> > +               default y if VENDOR_KM
> > 
> >                 help
> >                 
> >                   This option enables the old flavor of hush based on hush
> >                   Busybox from
> >                   2005.
> > 
> > @@ -37,6 +37,7 @@ menu "Hush flavor to use"
> > 
> >         config HUSH_2021_PARSER
> >         
> >                 bool "Use hush 2021 parser"
> > 
> > +               default y if !VENDOR_KM
> > 
> >                 help
> >                 
> >                   This option enables the new flavor of hush based on hush
> >                   Busybox from
> >                   2021.
> > 
> > diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
> > index 93796e87c5..78a13eeb62 100644
> > --- a/common/cli_hush_upstream.c
> > +++ b/common/cli_hush_upstream.c
> > @@ -427,7 +427,6 @@
> > 
> >  #include "NUM_APPLETS.h"
> >  #if NUM_APPLETS == 1
> >  /* STANDALONE does not make sense, and won't compile */
> > 
> > -# undef CONFIG_FEATURE_SH_STANDALONE
> > 
> >  # undef ENABLE_FEATURE_SH_STANDALONE
> >  # undef IF_FEATURE_SH_STANDALONE
> >  # undef IF_NOT_FEATURE_SH_STANDALONE
> > 
> > diff --git a/configs/bk4r1_defconfig b/configs/bk4r1_defconfig
> > index 66adeac725..95f0c30cde 100644
> > --- a/configs/bk4r1_defconfig
> > +++ b/configs/bk4r1_defconfig
> > @@ -18,6 +18,7 @@ CONFIG_TARGET_BK4R1=y
> > 
> >  CONFIG_SYS_LOAD_ADDR=0x82000000
> >  CONFIG_SYS_MEMTEST_START=0x80010000
> >  CONFIG_SYS_MEMTEST_END=0x87c00000
> > 
> > +CONFIG_LTO=y
> > 
> >  CONFIG_HAS_BOARD_SIZE_LIMIT=y
> >  CONFIG_BOARD_SIZE_LIMIT=520192
> >  CONFIG_FIT=y
> > 
> > diff --git a/configs/sheevaplug_defconfig b/configs/sheevaplug_defconfig
> > index 2e4901b840..365f779cc8 100644
> > --- a/configs/sheevaplug_defconfig
> > +++ b/configs/sheevaplug_defconfig
> > @@ -16,6 +16,7 @@ CONFIG_ENV_OFFSET=0x80000
> > 
> >  CONFIG_DEFAULT_DEVICE_TREE="kirkwood-sheevaplug"
> >  CONFIG_IDENT_STRING="\nMarvell-Sheevaplug"
> >  CONFIG_SYS_LOAD_ADDR=0x800000
> > 
> > +CONFIG_LTO=y
> > 
> >  CONFIG_HAS_BOARD_SIZE_LIMIT=y
> >  CONFIG_BOARD_SIZE_LIMIT=524288
> >  CONFIG_BOOTDELAY=3
> > 
> > --
> > 2.34.1


Best regards.



^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy
  2023-03-30 22:14     ` Francis Laniel
@ 2023-03-31 23:17       ` Tony Dinh
  0 siblings, 0 replies; 37+ messages in thread
From: Tony Dinh @ 2023-03-31 23:17 UTC (permalink / raw)
  To: Francis Laniel, Tom Rini, Pali Rohár, Stefan Roese
  Cc: u-boot, Michael Nazzareno Trimarchi, Simon Glass, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, John Keeping, Neil Armstrong, Roger Knecht,
	Sughosh Ganu, Alexey Romanov, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Linus Walleij, Rui Miguel Silva, Andrew Scull

Hi Francis,

On Thu, Mar 30, 2023 at 3:14 PM Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Hi.
>
> Le jeudi 30 mars 2023, 21:17:33 WEST Tony Dinh a écrit :
> > Hi Francis,
> >
> > On Thu, Mar 30, 2023 at 12:50 PM Francis Laniel
> >
> > <francis.laniel@amarulasolutions.com> wrote:
> > > This commit set CONFIG_HUSH_PARSER_2021 as the default to trigger the CI
> > > with this parser.
> > >
> > > Nonetheless, the keymile (i.e. VENDOR_KM) board family is not compatible
> > > with new 2021 hush parser.
> > > Indeed, This boards used set_local_var() to store some variables as local
> > > shell. They then used get_local_var() to retrieve the variables values.
> > > Sadly, this two functions do not exist with CONFIG_HUSH_PARSER_2021.
> > > A patch was proposed to use environment variables rather than local
> > > variables but it does not tackle the problem, so complementary work is
> > > needed to make this boards use CONFIG_HUSH_PARSER_2021 [1].
> > >
> > > We also remove a #undef of CONFIG_FEATURE_SH_STANDALONE as it does not
> > > exist in U-Boot and causes troubles in the CI.
> > >
> > > We also set CONFIG_LTO for kirkwoord sheevaplug and phytec bk4r1,
> > > otherwise it hits its board size limit.
> > > By enabling this option, we also had to add assembly for
> > > __gnu_thumb1_case_si. It was taken from upstream gcc and adapted as width
> > > suffix was removed for the add [2].
> >
> > Thanks for adding __gnu_thumb1_case_si ! It will help not just the
> > Sheevaplug board, but also a few other boards too. I'll give this a
> > try.
>
> You are welcome!
> I nonetheless cannot ensure the modifications I brought to the code are correct
> as I do not have the board to test them.
> So, if you can test it on a board it will be really welcomed and in case you
> find any problem I will add your suggested changes to the commit!

I applied the arch/arm/lib/lib1funcs.S patch, built, and ran the
u-boot-2023.04-rc5 for Pogo V4 board (Kirkwood 6192 SoC). Everything
is working fine. Thanks!

Is it possible for you to send in this patch for lib1funcs.S
separately? Given this RFC patch series probably will take a while to
get merged. In the meantime, it would be great to have this earlier so
we all can start building LTO+Thumb enabled u-boots with gcc 12.x. I
can do more testing with other boards that have CONFIG_HAS_THUMB2 and
CONFIG_SPL_SYS_THUMB_BUILD. Please let me know if I can help with that
individual patch.

Ref [1] https://lists.denx.de/pipermail/u-boot/2022-November/500460.html
Ref [2] https://lists.denx.de/pipermail/u-boot/2022-November/500463.html

Tested-by: Tony Dinh <mibodhi@gmail.com>

All the best,
Tony

>
> >
> > All the best,
> > Tony
> >
> > > Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> > > [1] https://marc.info/?l=u-boot&m=165541917618725&w=2
> > > [2]
> > > https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be901
> > > 8f1611/libgcc/config/arm/lib1funcs.S#L2156 ---
> > >
> > >  arch/arm/lib/lib1funcs.S     | 17 +++++++++++++++++
> > >  cmd/Kconfig                  |  3 ++-
> > >  common/cli_hush_upstream.c   |  1 -
> > >  configs/bk4r1_defconfig      |  1 +
> > >  configs/sheevaplug_defconfig |  1 +
> > >  5 files changed, 21 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S
> > > index de15d09e36..a1f44d9454 100644
> > > --- a/arch/arm/lib/lib1funcs.S
> > > +++ b/arch/arm/lib/lib1funcs.S
> > > @@ -419,4 +419,21 @@ ENTRY(__gnu_thumb1_case_uhi)
> > >
> > >         ret     lr
> > >
> > >  ENDPROC(__gnu_thumb1_case_uhi)
> > >  .popsection
> > >
> > > +
> > > +/* Taken and adapted from:
> > > https://github.com/gcc-mirror/gcc/blob/4f181f9c7ee3efc509d185fdfda33be901
> > > 8f1611/libgcc/config/arm/lib1funcs.S#L2156 */ +.pushsection
> > > .text.__gnu_thumb1_case_si, "ax"
> > > +ENTRY(__gnu_thumb1_case_si)
> > > +       push    {r0, r1}
> > > +       mov     r1, lr
> > > +       adds    r1, r1, #2      /* Align to word.  */
> > > +       lsrs    r1, r1, #2
> > > +       lsls    r0, r0, #2
> > > +       lsls    r1, r1, #2
> > > +       ldr     r0, [r1, r0]
> > > +       adds    r0, r0, r1
> > > +       mov     lr, r0
> > > +       pop     {r0, r1}
> > > +       mov     pc, lr          /* We know we were called from thumb code.
> > >  */ +ENDPROC(__gnu_thumb1_case_si)
> > > +.popsection
> > >
> > >  #endif
> > >
> > > diff --git a/cmd/Kconfig b/cmd/Kconfig
> > > index 1b9d04680d..d4809fcfa4 100644
> > > --- a/cmd/Kconfig
> > > +++ b/cmd/Kconfig
> > > @@ -28,7 +28,7 @@ menu "Hush flavor to use"
> > >
> > >         config HUSH_OLD_PARSER
> > >
> > >                 bool "Use hush old parser"
> > >
> > > -               default y
> > > +               default y if VENDOR_KM
> > >
> > >                 help
> > >
> > >                   This option enables the old flavor of hush based on hush
> > >                   Busybox from
> > >                   2005.
> > >
> > > @@ -37,6 +37,7 @@ menu "Hush flavor to use"
> > >
> > >         config HUSH_2021_PARSER
> > >
> > >                 bool "Use hush 2021 parser"
> > >
> > > +               default y if !VENDOR_KM
> > >
> > >                 help
> > >
> > >                   This option enables the new flavor of hush based on hush
> > >                   Busybox from
> > >                   2021.
> > >
> > > diff --git a/common/cli_hush_upstream.c b/common/cli_hush_upstream.c
> > > index 93796e87c5..78a13eeb62 100644
> > > --- a/common/cli_hush_upstream.c
> > > +++ b/common/cli_hush_upstream.c
> > > @@ -427,7 +427,6 @@
> > >
> > >  #include "NUM_APPLETS.h"
> > >  #if NUM_APPLETS == 1
> > >  /* STANDALONE does not make sense, and won't compile */
> > >
> > > -# undef CONFIG_FEATURE_SH_STANDALONE
> > >
> > >  # undef ENABLE_FEATURE_SH_STANDALONE
> > >  # undef IF_FEATURE_SH_STANDALONE
> > >  # undef IF_NOT_FEATURE_SH_STANDALONE
> > >
> > > diff --git a/configs/bk4r1_defconfig b/configs/bk4r1_defconfig
> > > index 66adeac725..95f0c30cde 100644
> > > --- a/configs/bk4r1_defconfig
> > > +++ b/configs/bk4r1_defconfig
> > > @@ -18,6 +18,7 @@ CONFIG_TARGET_BK4R1=y
> > >
> > >  CONFIG_SYS_LOAD_ADDR=0x82000000
> > >  CONFIG_SYS_MEMTEST_START=0x80010000
> > >  CONFIG_SYS_MEMTEST_END=0x87c00000
> > >
> > > +CONFIG_LTO=y
> > >
> > >  CONFIG_HAS_BOARD_SIZE_LIMIT=y
> > >  CONFIG_BOARD_SIZE_LIMIT=520192
> > >  CONFIG_FIT=y
> > >
> > > diff --git a/configs/sheevaplug_defconfig b/configs/sheevaplug_defconfig
> > > index 2e4901b840..365f779cc8 100644
> > > --- a/configs/sheevaplug_defconfig
> > > +++ b/configs/sheevaplug_defconfig
> > > @@ -16,6 +16,7 @@ CONFIG_ENV_OFFSET=0x80000
> > >
> > >  CONFIG_DEFAULT_DEVICE_TREE="kirkwood-sheevaplug"
> > >  CONFIG_IDENT_STRING="\nMarvell-Sheevaplug"
> > >  CONFIG_SYS_LOAD_ADDR=0x800000
> > >
> > > +CONFIG_LTO=y
> > >
> > >  CONFIG_HAS_BOARD_SIZE_LIMIT=y
> > >  CONFIG_BOARD_SIZE_LIMIT=524288
> > >  CONFIG_BOOTDELAY=3
> > >
> > > --
> > > 2.34.1
>
>
> Best regards.
>
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 04/23] test: hush: Test hush variable expansion
  2023-03-30 19:46 ` [RFC PATCH v7 04/23] test: hush: Test hush variable expansion Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Alexey Romanov, Marek Vasut,
	Fabrice Gasnier, Hector Palacios, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Linus Walleij, Andrew Scull

Hi Francis,


On Fri, 31 Mar 2023 at 08:48, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Verifies shell variables are replaced by their values.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  test/hush/Makefile       |   1 +
>  test/hush/dollar.c       | 167 +++++++++++++++++++++++++++++++++++++++
>  test/py/tests/test_ut.py |   8 +-
>  3 files changed, 175 insertions(+), 1 deletion(-)
>  create mode 100644 test/hush/dollar.c
>

Reviewed-by: Simon Glass <sjg@chromium.org>

nit below

> diff --git a/test/hush/Makefile b/test/hush/Makefile
> index a3c9ae5106..feb4f71956 100644
> --- a/test/hush/Makefile
> +++ b/test/hush/Makefile
> @@ -5,3 +5,4 @@
>
>  obj-y += cmd_ut_hush.o
>  obj-y += if.o
> +obj-y += dollar.o
> diff --git a/test/hush/dollar.c b/test/hush/dollar.c
> new file mode 100644
> index 0000000000..27feda2970
> --- /dev/null
> +++ b/test/hush/dollar.c
> @@ -0,0 +1,167 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * (C) Copyright 2021
> + * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
> + */
> +
> +#include <common.h>
> +#include <command.h>
> +#include <env_attr.h>
> +#include <test/hush.h>
> +#include <test/ut.h>
> +
> +static int hush_test_simple_dollar(struct unit_test_state *uts)
> +{
> +       console_record_reset_enable();
> +       ut_assertok(run_command("echo $dollar_foo", 0));
> +       ut_assert_nextline("%s", "");

ut_assert_nextline_empty();

> +       ut_assert_console_end();
> +
> +       ut_assertok(run_command("echo ${dollar_foo}", 0));
> +       ut_assert_nextline("%s", "");
> +       ut_assert_console_end();
> +

[..]

Regards,
Simon

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 09/23] cli: Add menu for hush parser
  2023-03-30 19:46 ` [RFC PATCH v7 09/23] cli: Add menu for hush parser Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Sughosh Ganu, Alexey Romanov,
	Patrice Chotard, Fabrice Gasnier, Hector Palacios, Marek Vasut,
	Rasmus Villemoes, AKASHI Takahiro, Rui Miguel Silva,
	Linus Walleij, Andrew Scull

On Fri, 31 Mar 2023 at 08:48, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> For the moment, the menu contains only entry: HUSH_OLD_PARSER which is the
> default.

It seems to contain both with this patch?

> The goal is to prepare the field to add a new hush parser which guarantees
> actual behavior is still correct.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  cmd/Kconfig     | 21 +++++++++++++++++++++
>  common/Makefile |  3 ++-
>  2 files changed, 23 insertions(+), 1 deletion(-)

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag
  2023-03-30 19:46 ` [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Samuel Dionne-Riel,
	Alexey Romanov, Marek Vasut, Fabrice Gasnier, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

On Fri, 31 Mar 2023 at 08:49, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> This flag is used to indicate we are using the hush parser.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  common/cli.c                      | 2 ++
>  include/asm-generic/global_data.h | 4 ++++
>  2 files changed, 6 insertions(+)

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 11/23] cmd: Add new parser command
  2023-03-30 19:46 ` [RFC PATCH v7 11/23] cmd: Add new parser command Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  2023-04-01 22:44     ` Francis Laniel
  0 siblings, 1 reply; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Robert Marko, Alexey Romanov,
	Fabrice Gasnier, Patrick Delaunay, Marek Vasut, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

Hi Francis,

On Fri, 31 Mar 2023 at 08:49, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> This command can be used to print the current parser with 'parser print'.
> It can also be used to set the current parser with 'parser set'.
> For the moment, only one value is valid for set: old.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  cmd/Makefile |   2 +
>  cmd/parser.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  common/cli.c |   3 +-
>  3 files changed, 124 insertions(+), 1 deletion(-)
>  create mode 100644 cmd/parser.c
>

Reviewed-by: Simon Glass <sjg@chromium.org>

But I wonder whether 'cli' might be a better name for this command? We
use cli everywhere else.

Also can you add doc/usage for this?

Regards,
Simon

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser
  2023-03-30 19:47 ` [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Heinrich Schuchardt, Ilias Apalodimas,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Robert Marko, Alexey Romanov,
	Marek Vasut, Patrick Delaunay, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Linus Walleij, Andrew Scull

On Fri, 31 Mar 2023 at 08:49, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> If one defines HUSH_2021_PARSER, it is then possible to use 2021 parser with:
> => parser get
> old
> => parser set 2021
> 2021> parser get
> 2021
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  cmd/parser.c                      |  7 ++++-
>  common/cli.c                      | 38 +++++++++++++++++++----
>  common/cli_hush_2021.c            |  3 ++
>  common/cli_hush_upstream.c        | 46 +++++++++++++++++++++++++---
>  include/asm-generic/global_data.h |  4 +++
>  include/cli_hush.h                | 51 +++++++++++++++++++++++++++++--
>  6 files changed, 135 insertions(+), 14 deletions(-)
>

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*()
  2023-03-30 19:47 ` [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*() Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Marek Vasut, Hector Palacios, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

Hi Francis,

On Fri, 31 Mar 2023 at 08:49, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Enables using, in code, hush 2021 as parser for run_command function family.
> It also enables the command run to be used by CLI user of hush 2021.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  common/cli.c               | 63 ++++++++++++++++++++++++++++----------
>  common/cli_hush_upstream.c |  2 +-
>  2 files changed, 48 insertions(+), 17 deletions(-)
>

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021
  2023-03-30 19:47 ` [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021 Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Sughosh Ganu, Linus Walleij,
	Alexey Romanov, Patrick Delaunay, Patrice Chotard,
	Fabrice Gasnier, Hector Palacios, Marek Vasut, Rasmus Villemoes,
	AKASHI Takahiro, Rui Miguel Silva, Andrew Scull

On Fri, 31 Mar 2023 at 08:49, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Modifies the expected result for hush 2021.
> Indeed, there were bugs in actual U-Boot hush which were fixed in upstream
> Busybox.
> As hush 2021 is based on upstream Busybox, these bugs no longer exist.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  test/hush/list.c | 69 +++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 65 insertions(+), 4 deletions(-)
>

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 17/23] test: hush: Fix variable expansion tests for hush 2021
  2023-03-30 19:47 ` [RFC PATCH v7 17/23] test: hush: Fix variable expansion " Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrick Delaunay, Patrice Chotard, Fabrice Gasnier,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

On Fri, 31 Mar 2023 at 08:50, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Modifies the expected result for hush 2021.
> Indeed, there were bugs in actual U-Boot hush which were fixed in upstream
> Busybox.
> As hush 2021 is based on upstream Busybox, these bugs no longer exist.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  test/hush/dollar.c | 79 ++++++++++++++++++++++++++++++++++++++++------
>  1 file changed, 69 insertions(+), 10 deletions(-)

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021
  2023-03-30 19:47 ` [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021 Francis Laniel
@ 2023-04-01  6:32   ` Simon Glass
  0 siblings, 0 replies; 37+ messages in thread
From: Simon Glass @ 2023-04-01  6:32 UTC (permalink / raw)
  To: Francis Laniel
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Neil Armstrong, Roger Knecht, Linus Walleij, Alexey Romanov,
	Patrick Delaunay, Fabrice Gasnier, Patrice Chotard,
	Hector Palacios, Marek Vasut, Rasmus Villemoes, AKASHI Takahiro,
	Rui Miguel Silva, Andrew Scull

On Fri, 31 Mar 2023 at 08:50, Francis Laniel
<francis.laniel@amarulasolutions.com> wrote:
>
> Modifies return code got from while loop as hush 2021 always returns 0 from
> while loop.
>
> Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> ---
>  test/hush/loop.c | 34 ++++++++++++++++++++++++++++++----
>  1 file changed, 30 insertions(+), 4 deletions(-)
>

Reviewed-by: Simon Glass <sjg@chromium.org>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [RFC PATCH v7 11/23] cmd: Add new parser command
  2023-04-01  6:32   ` Simon Glass
@ 2023-04-01 22:44     ` Francis Laniel
  0 siblings, 0 replies; 37+ messages in thread
From: Francis Laniel @ 2023-04-01 22:44 UTC (permalink / raw)
  To: Simon Glass
  Cc: u-boot, Michael Nazzareno Trimarchi, Tom Rini, Harald Seiler,
	Albert ARIBAUD (3ADEV),
	Tony Dinh, Sergei Antonov, Ilias Apalodimas, Heinrich Schuchardt,
	Masahisa Kojima, Pali Rohár, Stefan Roese, John Keeping,
	Roger Knecht, Neil Armstrong, Robert Marko, Alexey Romanov,
	Fabrice Gasnier, Patrick Delaunay, Marek Vasut, Hector Palacios,
	Rasmus Villemoes, AKASHI Takahiro, Linus Walleij,
	Rui Miguel Silva, Andrew Scull

Hi.


Le samedi 1 avril 2023, 07:32:14 WEST Simon Glass a écrit :
> Hi Francis,
> 
> On Fri, 31 Mar 2023 at 08:49, Francis Laniel
> 
> <francis.laniel@amarulasolutions.com> wrote:
> > This command can be used to print the current parser with 'parser print'.
> > It can also be used to set the current parser with 'parser set'.
> > For the moment, only one value is valid for set: old.
> > 
> > Signed-off-by: Francis Laniel <francis.laniel@amarulasolutions.com>
> > ---
> > 
> >  cmd/Makefile |   2 +
> >  cmd/parser.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >  common/cli.c |   3 +-
> >  3 files changed, 124 insertions(+), 1 deletion(-)
> >  create mode 100644 cmd/parser.c
> 
> Reviewed-by: Simon Glass <sjg@chromium.org>

Thank you for the reviews!

> 
> But I wonder whether 'cli' might be a better name for this command? We
> use cli everywhere else.

"Naming thing is hard" [1].
Thank you for the suggestion! I changed it to cli which I think is a better 
name as it is less abstract than parser.

> Also can you add doc/usage for this?

Done! I was not aware of doc/usage/cmd before.

> Regards,
> Simon

By the way, I also addressed your other comments on patches 4 and 9.


Best regards.
---
[1] https://www.karlton.org/2017/12/naming-things-hard/



^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2023-04-01 22:44 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-30 19:46 [RFC PATCH v7 00/23] Modernize U-Boot shell Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 01/23] test: Add framework to test hush behavior Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 02/23] test: hush: Test hush if/else Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 03/23] test/py: hush_if_test: Remove the test file Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 04/23] test: hush: Test hush variable expansion Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:46 ` [RFC PATCH v7 05/23] test: hush: Test hush commands list Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 06/23] test: hush: Test hush loops Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 07/23] cli: Add Busybox upstream hush.c file Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 08/23] cli: Port Busybox 2021 hush to U-Boot Francis Laniel
2023-03-30 19:46 ` [RFC PATCH v7 09/23] cli: Add menu for hush parser Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:46 ` [RFC PATCH v7 10/23] global_data.h: add GD_FLG_HUSH_OLD_PARSER flag Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:46 ` [RFC PATCH v7 11/23] cmd: Add new parser command Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-04-01 22:44     ` Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 12/23] cli: Enables using hush 2021 parser as command line parser Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:47 ` [RFC PATCH v7 13/23] cli: hush_2021: Enable variables expansion for hush 2021 Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 14/23] cli: hush_2021: Add functions to be called from run_command() Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 15/23] cli: add hush 2021 as parser for run_command*() Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:47 ` [RFC PATCH v7 16/23] test: hush: Fix instructions list tests for hush 2021 Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:47 ` [RFC PATCH v7 17/23] test: hush: Fix variable expansion " Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:47 ` [RFC PATCH v7 18/23] cli: hush_2021: Enable using < and > as string compare operators Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 19/23] cli: hush_2021: Enable if keyword Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 20/23] cli: hush_2021: Enable loops Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 21/23] test: hush: Fix loop tests for hush 2021 Francis Laniel
2023-04-01  6:32   ` Simon Glass
2023-03-30 19:47 ` [RFC PATCH v7 22/23] cli: hush_2021: Add upstream commits up to 30th January 2023 Francis Laniel
2023-03-30 19:47 ` [RFC PATCH v7 23/23] DO NOT MERGE: only to make CI happy Francis Laniel
2023-03-30 20:17   ` Tony Dinh
2023-03-30 22:14     ` Francis Laniel
2023-03-31 23:17       ` Tony Dinh

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).