--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0+
+
+ #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_name(void)
+ {
+ if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
+ return "old";
+ if (gd->flags & GD_FLG_HUSH_MODERN_PARSER)
+ return "modern";
+ return NULL;
+ }
+
+ static int do_cli_get(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+ {
+ const char *current = gd_flags_to_parser_name();
+
+ if (!current) {
+ printf("current cli 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;
+ if (!strcmp(parser, "modern"))
+ return GD_FLG_HUSH_MODERN_PARSER;
+ return -1;
+ }
+
+ static int gd_flags_to_parser_config(int flag)
+ {
+ if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
+ return CONFIG_VAL(HUSH_OLD_PARSER);
+ if (gd->flags & GD_FLG_HUSH_MODERN_PARSER)
+ return CONFIG_VAL(HUSH_MODERN_PARSER);
+ return -1;
+ }
+
+ static void reset_parser_gd_flags(void)
+ {
+ gd->flags &= ~GD_FLG_HUSH_OLD_PARSER;
+ gd->flags &= ~GD_FLG_HUSH_MODERN_PARSER;
+ }
+
+ static int do_cli_set(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+ {
+ char *parser_name;
+ int parser_config;
+ 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;
+ }
+
+ parser_config = gd_flags_to_parser_config(parser_flag);
+ switch (parser_config) {
+ case -1:
+ printf("Bad value for parser flags: %d\n", parser_flag);
+ return CMD_RET_FAILURE;
+ case 0:
+ printf("Want to set current parser to %s, but its code was not compiled!\n",
+ parser_name);
+ 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_cli_get, "", ""),
+ U_BOOT_CMD_MKENT(set, 2, 1, do_cli_set, "", ""),
+ };
+
+ static int do_cli(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 cli_help_text[] =
+ "get - print current cli\n"
+ "set - set the current cli, possible value are: old, modern"
+ ;
+ #endif
+
+ U_BOOT_CMD(cli, 3, 1, do_cli,
+ "cli",
+ #if CONFIG_IS_ENABLED(SYS_LONGHELP)
+ cli_help_text
+ #endif
+ );
--- /dev/null
-#include <common.h> /* readline */
+ // 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_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 <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.35.0.git7d1c7d833785"
+
+ /*
+ * Define hush features by the names used upstream.
+ */
+ #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__
+ #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 bb_simple_error_msg(const char *s)
+ {
+ bb_error_msg("%s", s);
+ }
+
+ 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;
+ }
+
+ static void *xmemdup(const void *s, int n)
+ {
+ return memcpy(xmalloc(n), s, n);
+ }
+
+ #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;
+ }
+
+ /**
+ * 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;
+ }
+
+ static int varcmp(const char *p, const char *q)
+ {
+ int c, d;
+
+ while ((c = *p) == (d = *q)) {
+ if (c == '\0' || c == '=')
+ goto out;
+ p++;
+ q++;
+ }
+ if (c == '=')
+ c = '\0';
+ if (d == '=')
+ d = '\0';
+ out:
+ return c - d;
+ }
+
+ 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 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.
+ */
+ #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_modern(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);
+ }
--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+ #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);
+ }
--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+ #include <command.h>
+ #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)
+ {
+ console_record_reset_enable();
+ ut_assertok(run_command("echo $dollar_foo", 0));
+ ut_assert_nextline_empty();
+ ut_assert_console_end();
+
+ ut_assertok(run_command("echo ${dollar_foo}", 0));
+ ut_assert_nextline_empty();
+ 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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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));
+
+ 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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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;
+ }
+ 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);
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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;
+ }
+ 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));
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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));
+
+ 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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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;
+ }
+ HUSH_TEST(hush_test_command_dollar, 0);
--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+ #include <command.h>
+ #include <env_attr.h>
++#include <vsprintf.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");
+ env_set("ut_var_unset", "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));
+
+ sprintf(if_formatted, if_format, "test -z \"$ut_var_unset\"");
+ ut_asserteq(1, run_command(if_formatted, 0));
+
+ env_set("ut_var_unset", NULL);
+ sprintf(if_formatted, if_format, "test -z \"$ut_var_unset\"");
+ ut_assertok(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);
--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+ #include <command.h>
+ #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)
+ {
+ /* 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);
+
+ 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));
+
+ if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+ ut_asserteq(1, run_command("false && false || true", 0));
+ } else if (gd->flags & GD_FLG_HUSH_MODERN_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_MODERN_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));
+ 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));
+
+ if (gd->flags & GD_FLG_HUSH_OLD_PARSER) {
+ ut_assertok(run_command("true || true && false", 0));
+ } else if (gd->flags & GD_FLG_HUSH_MODERN_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_MODERN_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));
+
+ return 0;
+ }
+ HUSH_TEST(hush_test_or_and, 0);
--- /dev/null
-#include <common.h>
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+ * (C) Copyright 2021
+ * Francis Laniel, Amarula Solutions, francis.laniel@amarulasolutions.com
+ */
+
+ #include <command.h>
+ #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)
+ {
+ 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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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;
+ }
+ HUSH_TEST(hush_test_for, 0);
+
+ static int hush_test_while(struct unit_test_state *uts)
+ {
+ console_record_reset_enable();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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();
+
+ if (gd->flags & GD_FLG_HUSH_MODERN_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;
+ }
+ 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);