(Note: this is a large patch. Patch included as mail attachment)

All of the bash compatibility options will now depend on
CONFIG_*_BASH_COMPAT, and CONFIG_*_BASH_COMPAT become an option that
alone doesn't add any code.

Splitting these options allows more flexibility in configuration, and
makes it self-documenting about what bash-compatible features we have.

Signed-off-by: Kang-Che Sung <[email protected]>
---
 coreutils/test.c |   8 ++-
 shell/ash.c      | 216 +++++++++++++++++++++++++++++++++++++------------------
 shell/hush.c     | 133 ++++++++++++++++++++++------------
 3 files changed, 239 insertions(+), 118 deletions(-)
From 655b1f7dd97c83c79502a0635fe9d195de47beaa Mon Sep 17 00:00:00 2001
From: Kang-Che Sung <[email protected]>
Date: Wed, 11 Jan 2017 03:02:24 +0800
Subject: [PATCH 2/2] Split bash compatible extensions into separate options.

All of the bash compatibility options will now depend on
CONFIG_*_BASH_COMPAT, and CONFIG_*_BASH_COMPAT become an option that
alone doesn't add any code.

Splitting these options allows more flexibility in configuration, and
makes it self-documenting about what bash-compatible features we have.

Signed-off-by: Kang-Che Sung <[email protected]>
---
 coreutils/test.c |   8 ++-
 shell/ash.c      | 216 +++++++++++++++++++++++++++++++++++++------------------
 shell/hush.c     | 133 ++++++++++++++++++++++------------
 3 files changed, 239 insertions(+), 118 deletions(-)

diff --git a/coreutils/test.c b/coreutils/test.c
index edc625f57..704255630 100644
--- a/coreutils/test.c
+++ b/coreutils/test.c
@@ -42,7 +42,7 @@
 //config:config FEATURE_TEST_64
 //config:	bool "Extend test to 64 bit"
 //config:	default y
-//config:	depends on TEST || TEST1 || TEST2 || ASH_TEST || HUSH_TEST
+//config:	depends on TEST || TEST1 || TEST2 || ASH_TEST || ASH_TEST2 || HUSH_TEST || HUSH_TEST2
 //config:	help
 //config:	  Enable 64-bit support in test.
 
@@ -54,8 +54,10 @@
 //kbuild:lib-$(CONFIG_TEST1) += test.o test_ptr_hack.o
 //kbuild:lib-$(CONFIG_TEST2) += test.o test_ptr_hack.o
 
-//kbuild:lib-$(CONFIG_ASH_TEST)  += test.o test_ptr_hack.o
-//kbuild:lib-$(CONFIG_HUSH_TEST) += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_ASH_TEST)   += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_ASH_TEST2)  += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_HUSH_TEST)  += test.o test_ptr_hack.o
+//kbuild:lib-$(CONFIG_HUSH_TEST2) += test.o test_ptr_hack.o
 
 /* "test --help" is special-cased to ignore --help */
 //usage:#define test_trivial_usage NOUSAGE_STR
diff --git a/shell/ash.c b/shell/ash.c
index 866c7de05..d5f6b7d04 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -47,11 +47,6 @@
 //config:	  Note that as of now (2017-01), uclibc and musl glob() both have bugs
 //config:	  which would break ash if you select N here.
 //config:
-//config:config ASH_BASH_COMPAT
-//config:	bool "bash-compatible extensions"
-//config:	default y
-//config:	depends on ASH || SH_IS_ASH || BASH_IS_ASH
-//config:
 //config:config ASH_JOB_CONTROL
 //config:	bool "Job control"
 //config:	default y
@@ -133,6 +128,76 @@
 //config:	  you to run the specified command or builtin,
 //config:	  even when there is a function with the same name.
 //config:
+//config:config ASH_BASH_COMPAT
+//config:	bool "bash-compatible extensions"
+//config:	default y
+//config:	depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_DOLLAR_SQUOTE
+//config:	bool "Dollar single quote $'...'"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
+//config:config ASH_SUBSTR_EXPANSION
+//config:	bool "Substring expansion ${var:position:length}"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
+//config:config ASH_PATTERN_SUBST
+//config:	bool "Pattern substitution ${var/pattern/replacement}"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
+//config:config ASH_REDIR_OUTPUT2
+//config:	bool "Redirection operator &>file (equivalent to '>file 2>&1')"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
+//config:config ASH_FUNCTION2
+//config:	bool "function keyword"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:	help
+//config:	  Enable the 'function' keyword to define a function in
+//config:	  addition to the standard 'funcname() { commands; }' syntax.
+//config:	  'function' keyword is bash-specific.
+//config:
+//config:config ASH_SOURCE
+//config:	bool "source builtin"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:	help
+//config:	  Enable source builtin, which is equivalent to . (dot) builtin.
+//config:	  The availabilty of . (dot) builtin is unaffected by this option.
+//config:
+//config:config ASH_TEST2
+//config:	bool "test construct [[ EXPR ]]"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:	help
+//config:	  The test construct [[ EXPR ]] support in ash is currently
+//config:	  limited to allowing unquoted && and || operators within and not
+//config:	  treating them as command delimiters.
+/*            ^^^^^ TODO: update this if new feature gets implemented ^^^^^ */
+//config:
+//config:config ASH_OPT_PIPEFAIL
+//config:	bool "pipefail option"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:	help
+//config:	  Allow setting the pipefail option ('ash -o pipefail' or
+//config:	  'set -o pipefail'). pipefail is disabled by default at runtime.
+//config:
+//config:config ASH_HOSTNAME_VAR
+//config:	bool "$HOSTNAME variable"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
+//config:config ASH_SHLVL_VAR
+//config:	bool "$SHLVL variable"
+//config:	default y
+//config:	depends on ASH_BASH_COMPAT
+//config:
 //config:endif # ash options
 
 //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
@@ -250,7 +315,7 @@ static const char *const optletters_optnames[] = {
 	"b"   "notify",
 	"u"   "nounset",
 	"\0"  "vi"
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_OPT_PIPEFAIL
 	,"\0"  "pipefail"
 #endif
 #if DEBUG
@@ -327,14 +392,14 @@ struct globals_misc {
 #define bflag optlist[11]
 #define uflag optlist[12]
 #define viflag optlist[13]
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_OPT_PIPEFAIL
 # define pipefail optlist[14]
 #else
 # define pipefail 0
 #endif
 #if DEBUG
-# define nolog optlist[14 + ENABLE_ASH_BASH_COMPAT]
-# define debug optlist[15 + ENABLE_ASH_BASH_COMPAT]
+# define nolog optlist[14 + ENABLE_ASH_OPT_PIPEFAIL]
+# define debug optlist[15 + ENABLE_ASH_OPT_PIPEFAIL]
 #endif
 
 	/* trap handler commands */
@@ -655,8 +720,10 @@ out2str(const char *p)
 #define VSTRIMLEFT      0x8     /* ${var#pattern} */
 #define VSTRIMLEFTMAX   0x9     /* ${var##pattern} */
 #define VSLENGTH        0xa     /* ${#var} */
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SUBSTR_EXPANSION
 #define VSSUBSTR        0xc     /* ${var:position:length} */
+#endif
+#if ENABLE_ASH_PATTERN_SUBST
 #define VSREPLACE       0xd     /* ${var/pattern/replacement} */
 #define VSREPLACEALL    0xe     /* ${var//pattern/replacement} */
 #endif
@@ -683,7 +750,7 @@ static const char dolatstr[] ALIGN1 = {
 #define NDEFUN   14
 #define NARG     15
 #define NTO      16
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 #define NTO2     17
 #endif
 #define NCLOBBER 18
@@ -1093,7 +1160,7 @@ shcmd(union node *cmd, FILE *fp)
 		case NTO:      s = ">>"+1; dftfd = 1; break;
 		case NCLOBBER: s = ">|"; dftfd = 1; break;
 		case NAPPEND:  s = ">>"; dftfd = 1; break;
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 		case NTO2:
 #endif
 		case NTOFD:    s = ">&"; dftfd = 1; break;
@@ -4455,7 +4522,8 @@ cmdputs(const char *s)
 	static const char vstype[VSTYPE + 1][3] = {
 		"", "}", "-", "+", "?", "=",
 		"%", "%%", "#", "##"
-		IF_ASH_BASH_COMPAT(, ":", "/", "//")
+		IF_ASH_SUBSTR_EXPANSION(, ":")
+		IF_ASH_PATTERN_SUBST(, "/", "//")
 	};
 
 	const char *p, *str;
@@ -4682,7 +4750,7 @@ cmdtxt(union node *n)
 	case NAPPEND:
 		p = ">>";
 		goto redir;
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	case NTO2:
 #endif
 	case NTOFD:
@@ -5209,7 +5277,7 @@ openredirect(union node *redir)
 			goto ecreate;
 		break;
 	case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	case NTO2:
 #endif
 		/* Take care of noclobber mode. */
@@ -5370,7 +5438,7 @@ redirect(union node *redir, int flags)
 		union node *tmp = redir;
 		do {
 			sv_pos++;
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 			if (tmp->nfile.type == NTO2)
 				sv_pos++;
 #endif
@@ -5412,7 +5480,7 @@ redirect(union node *redir, int flags)
 				continue;
 			}
 		}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
  redirect_more:
 #endif
 		if (need_to_remember(sv, fd)) {
@@ -5465,12 +5533,12 @@ redirect(union node *redir, int flags)
 			}
 		} else if (fd != newfd) { /* move newfd to fd */
 			dup2_or_raise(newfd, fd);
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 			if (!(redir->nfile.type == NTO2 && fd == 2))
 #endif
 				close(newfd);
 		}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 		if (redir->nfile.type == NTO2 && fd == 1) {
 			/* We already redirected it to fd 1, now copy it to 2 */
 			newfd = 1;
@@ -5787,15 +5855,15 @@ static char *
 rmescapes(char *str, int flag)
 {
 	static const char qchars[] ALIGN1 = {
-		IF_ASH_BASH_COMPAT('/',) CTLESC, CTLQUOTEMARK, '\0' };
+		IF_ASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
 
 	char *p, *q, *r;
 	unsigned inquotes;
 	unsigned protect_against_glob;
 	unsigned globbing;
-	IF_ASH_BASH_COMPAT(unsigned slash = flag & RMESCAPE_SLASH;)
+	IF_ASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;)
 
-	p = strpbrk(str, qchars IF_ASH_BASH_COMPAT(+ !slash));
+	p = strpbrk(str, qchars IF_ASH_PATTERN_SUBST(+ !slash));
 	if (!p)
 		return str;
 
@@ -5847,7 +5915,7 @@ rmescapes(char *str, int flag)
 			protect_against_glob = 0;
 			goto copy;
 		}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_PATTERN_SUBST
 		else if (*p == '/' && slash) {
 			/* stop handling globbing and mark location of slash */
 			globbing = slash = 0;
@@ -6494,10 +6562,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 	char *loc;
 	char *rmesc, *rmescend;
 	char *str;
-	IF_ASH_BASH_COMPAT(char *repl = NULL;)
-	IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
+	IF_ASH_SUBSTR_EXPANSION(int pos, len, orig_len;)
 	int amount, resetloc;
-	IF_ASH_BASH_COMPAT(int workloc;)
+	IF_ASH_PATTERN_SUBST(int workloc;)
+	IF_ASH_PATTERN_SUBST(char *repl = NULL;)
 	int zero;
 	char *(*scan)(char*, char*, char*, char*, int, int);
 
@@ -6522,7 +6590,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		varunset(p, varname, startp, varflags);
 		/* NOTREACHED */
 
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SUBSTR_EXPANSION
 	case VSSUBSTR:
 //TODO: support more general format ${v:EXPR:EXPR},
 // where EXPR follows $(()) rules
@@ -6591,17 +6659,19 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		amount = loc - expdest;
 		STADJUST(amount, expdest);
 		return loc;
-#endif
+#endif /* ASH_SUBSTR_EXPANSION */
 	}
 
 	resetloc = expdest - (char *)stackblock();
 
+#if ENABLE_ASH_PATTERN_SUBST
 	/* We'll comeback here if we grow the stack while handling
 	 * a VSREPLACE or VSREPLACEALL, since our pointers into the
 	 * stack will need rebasing, and we'll need to remove our work
 	 * areas each time
 	 */
- IF_ASH_BASH_COMPAT(restart:)
+ restart:
+#endif
 
 	amount = expdest - ((char *)stackblock() + resetloc);
 	STADJUST(-amount, expdest);
@@ -6626,11 +6696,11 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 	 * RMESCAPE_SLASH causes preglob to work differently on the pattern
 	 * and string.  It's only used on the first call.
 	 */
-	preglob(str, IF_ASH_BASH_COMPAT(
+	preglob(str, IF_ASH_PATTERN_SUBST(
 		(subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ?
-			RMESCAPE_SLASH :) 0);
+			RMESCAPE_SLASH : ) 0);
 
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_PATTERN_SUBST
 	workloc = expdest - (char *)stackblock();
 	if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
 		char *idx, *end;
@@ -6731,7 +6801,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 		STADJUST(-amount, expdest);
 		return startp;
 	}
-#endif /* ENABLE_ASH_BASH_COMPAT */
+#endif /* ASH_PATTERN_SUBST */
 
 	subtype -= VSTRIMRIGHT;
 #if DEBUG
@@ -6999,8 +7069,10 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
 	case VSTRIMLEFTMAX:
 	case VSTRIMRIGHT:
 	case VSTRIMRIGHTMAX:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SUBSTR_EXPANSION
 	case VSSUBSTR:
+#endif
+#if ENABLE_ASH_PATTERN_SUBST
 	case VSREPLACE:
 	case VSREPLACEALL:
 #endif
@@ -7924,7 +7996,7 @@ enum {
 	TESAC,
 	TFI,
 	TFOR,
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_FUNCTION2
 	TFUNCTION,
 #endif
 	TIF,
@@ -7962,7 +8034,7 @@ enum {
 	/* 19 */ | (1u << TESAC)
 	/* 20 */ | (1u << TFI)
 	/* 21 */ | (0u << TFOR)
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_FUNCTION2
 	/* 22 */ | (0u << TFUNCTION)
 #endif
 	/* 23 */ | (0u << TIF)
@@ -8000,7 +8072,7 @@ static const char *const tokname_array[] = {
 	"esac",
 	"fi",
 	"for",
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_FUNCTION2
 	"function",
 #endif
 	"if",
@@ -8244,7 +8316,7 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
 	[NDEFUN   ] = SHELL_ALIGN(sizeof(struct narg)),
 	[NARG     ] = SHELL_ALIGN(sizeof(struct narg)),
 	[NTO      ] = SHELL_ALIGN(sizeof(struct nfile)),
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	[NTO2     ] = SHELL_ALIGN(sizeof(struct nfile)),
 #endif
 	[NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)),
@@ -8326,7 +8398,7 @@ calcsize(int funcblocksize, union node *n)
 		funcblocksize = calcsize(funcblocksize, n->narg.next);
 		break;
 	case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	case NTO2:
 #endif
 	case NCLOBBER:
@@ -8440,7 +8512,7 @@ copynode(union node *n)
 		new->narg.next = copynode(n->narg.next);
 		break;
 	case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	case NTO2:
 #endif
 	case NCLOBBER:
@@ -8873,14 +8945,14 @@ expredir(union node *n)
 		case NFROMTO:
 		case NFROM:
 		case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 		case NTO2:
 #endif
 		case NCLOBBER:
 		case NAPPEND:
 			expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
 			TRACE(("expredir expanded to '%s'\n", fn.list->text));
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
  store_expfname:
 #endif
 #if 0
@@ -8902,7 +8974,7 @@ expredir(union node *n)
 				expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
 				if (fn.list == NULL)
 					ash_msg_and_raise_error("redir error");
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 //FIXME: we used expandarg with different args!
 				if (!isdigit_str9(fn.list->text)) {
 					/* >&file, not >&fd */
@@ -9298,7 +9370,7 @@ static int FAST_FUNC echocmd(int argc, char **argv)   { return echo_main(argc, a
 #if ENABLE_ASH_PRINTF
 static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); }
 #endif
-#if ENABLE_ASH_TEST
+#if ENABLE_ASH_TEST || ENABLE_ASH_TEST2
 static int FAST_FUNC testcmd(int argc, char **argv)   { return test_main(argc, argv); }
 #endif
 
@@ -9308,9 +9380,9 @@ static const struct builtincmd builtintab[] = {
 	{ BUILTIN_SPEC_REG      ":"       , truecmd    },
 #if ENABLE_ASH_TEST
 	{ BUILTIN_REGULAR       "["       , testcmd    },
-# if ENABLE_ASH_BASH_COMPAT
+#endif
+#if ENABLE_ASH_TEST2
 	{ BUILTIN_REGULAR       "[["      , testcmd    },
-# endif
 #endif
 #if ENABLE_ASH_ALIAS
 	{ BUILTIN_REG_ASSG      "alias"   , aliascmd   },
@@ -9363,7 +9435,7 @@ static const struct builtincmd builtintab[] = {
 	{ BUILTIN_SPEC_REG      "return"  , returncmd  },
 	{ BUILTIN_SPEC_REG      "set"     , setcmd     },
 	{ BUILTIN_SPEC_REG      "shift"   , shiftcmd   },
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SOURCE
 	{ BUILTIN_SPEC_REG      "source"  , dotcmd     },
 #endif
 #if ENABLE_ASH_TEST
@@ -9386,7 +9458,7 @@ static const struct builtincmd builtintab[] = {
 #define COMMANDCMD (builtintab + \
 	/* . : */	2 + \
 	/* [ */		1 * ENABLE_ASH_TEST + \
-	/* [[ */	1 * ENABLE_ASH_TEST * ENABLE_ASH_BASH_COMPAT + \
+	/* [[ */	1 * ENABLE_ASH_TEST2 + \
 	/* alias */	1 * ENABLE_ASH_ALIAS + \
 	/* bg */	1 * ENABLE_ASH_JOB_CONTROL + \
 	/* break cd cddir  */	3)
@@ -11008,10 +11080,8 @@ simplecmd(void)
 	union node *vars, **vpp;
 	union node **rpp, *redir;
 	int savecheckkwd;
-#if ENABLE_ASH_BASH_COMPAT
-	smallint double_brackets_flag = 0;
-	smallint function_flag = 0;
-#endif
+	IF_ASH_TEST2(smallint double_brackets_flag = 0;)
+	IF_ASH_FUNCTION2(smallint function_flag = 0;)
 
 	args = NULL;
 	app = &args;
@@ -11026,12 +11096,14 @@ simplecmd(void)
 		checkkwd = savecheckkwd;
 		t = readtoken();
 		switch (t) {
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_FUNCTION2
 		case TFUNCTION:
 			if (peektoken() != TWORD)
 				raise_error_unexpected_syntax(TWORD);
 			function_flag = 1;
 			break;
+#endif
+#if ENABLE_ASH_TEST2
 		case TAND: /* "&&" */
 		case TOR: /* "||" */
 			if (!double_brackets_flag) {
@@ -11045,7 +11117,7 @@ simplecmd(void)
 			n->type = NARG;
 			/*n->narg.next = NULL; - stzalloc did it */
 			n->narg.text = wordtext;
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_TEST2
 			if (strcmp("[[", wordtext) == 0)
 				double_brackets_flag = 1;
 			else if (strcmp("]]", wordtext) == 0)
@@ -11060,7 +11132,7 @@ simplecmd(void)
 				app = &n->narg.next;
 				savecheckkwd = 0;
 			}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_FUNCTION2
 			if (function_flag) {
 				checkkwd = CHKNL | CHKKWD;
 				switch (peektoken()) {
@@ -11090,7 +11162,7 @@ simplecmd(void)
 			parsefname();   /* read name of redirection file */
 			break;
 		case TLP:
- IF_ASH_BASH_COMPAT(do_func:)
+ IF_ASH_FUNCTION2(do_func:)
 			if (args && app == &args->narg.next
 			 && !vars && !redir
 			) {
@@ -11098,7 +11170,7 @@ simplecmd(void)
 				const char *name;
 
 				/* We have a function */
-				if (IF_ASH_BASH_COMPAT(!function_flag &&) readtoken() != TRP)
+				if (IF_ASH_FUNCTION2(!function_flag &&) readtoken() != TRP)
 					raise_error_unexpected_syntax(TRP);
 				name = n->narg.text;
 				if (!goodname(name)
@@ -11111,7 +11183,7 @@ simplecmd(void)
 				n->narg.next = parse_command();
 				return n;
 			}
-			IF_ASH_BASH_COMPAT(function_flag = 0;)
+			IF_ASH_FUNCTION2(function_flag = 0;)
 			/* fall through */
 		default:
 			tokpushback = 1;
@@ -11292,7 +11364,7 @@ parse_command(void)
 		n1 = list(0);
 		t = TEND;
 		break;
-	IF_ASH_BASH_COMPAT(case TFUNCTION:)
+	IF_ASH_FUNCTION2(case TFUNCTION:)
 	case TWORD:
 	case TREDIR:
 		tokpushback = 1;
@@ -11325,7 +11397,7 @@ parse_command(void)
 	return n1;
 }
 
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_DOLLAR_SQUOTE
 static int
 decode_dollar_squote(void)
 {
@@ -11410,7 +11482,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 	IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
 	int dqvarnest;       /* levels of variables expansion within double quotes */
 
-	IF_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;)
+	IF_ASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
 
 	startlinno = g_parsefile->linno;
 	bqlist = NULL;
@@ -11445,7 +11517,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 			USTPUTC(c, out);
 			break;
 		case CCTL:
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_DOLLAR_SQUOTE
 			if (c == '\\' && bash_dollar_squote) {
 				c = decode_dollar_squote();
 				if (c == '\0') {
@@ -11506,7 +11578,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 			dblquote = 1;
 			goto quotemark;
 		case CENDQUOTE:
-			IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+			IF_ASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
 			if (eofmark != NULL && varnest == 0) {
 				USTPUTC(c, out);
 			} else {
@@ -11565,7 +11637,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 			break;
 		default:
 			if (varnest == 0) {
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 				if (c == '&') {
 //Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
 					if (pgetc() == '>')
@@ -11597,7 +11669,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 	len = out - (char *)stackblock();
 	out = stackblock();
 	if (eofmark == NULL) {
-		if ((c == '>' || c == '<' IF_ASH_BASH_COMPAT( || c == 0x100 + '>'))
+		if ((c == '>' || c == '<' IF_ASH_REDIR_OUTPUT2( || c == 0x100 + '>'))
 		 && quotef == 0
 		) {
 			if (isdigit_str9(out)) {
@@ -11685,7 +11757,7 @@ parseredir: {
 			pungetc();
 		}
 	}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 	else if (c == 0x100 + '>') { /* this flags &> redirection */
 		np->nfile.fd = 1;
 		pgetc(); /* this is '>', no need to check */
@@ -11751,7 +11823,7 @@ parsesub: {
 	if (c > 255 /* PEOA or PEOF */
 	 || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
 	) {
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_DOLLAR_SQUOTE
 		if (syntax != DQSYNTAX && c == '\'')
 			bash_dollar_squote = 1;
 		else
@@ -11827,7 +11899,7 @@ parsesub: {
 			switch (c) {
 			case ':':
 				c = pgetc_eatbnl();
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SUBSTR_EXPANSION
 				/* This check is only needed to not misinterpret
 				 * ${VAR:-WORD}, ${VAR:+WORD}, ${VAR:=WORD}, ${VAR:?WORD}
 				 * constructs.
@@ -11857,7 +11929,7 @@ parsesub: {
 				subtype++;
 				break;
 			}
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_PATTERN_SUBST
 			case '/':
 				/* ${v/[/]pattern/repl} */
 //TODO: encode pattern and repl separately.
@@ -12112,7 +12184,7 @@ xxreadtoken(void)
 						p += xxreadtoken_doubles + 1;
 					} else {
 						pungetc();
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_REDIR_OUTPUT2
 						if (c == '&' && cc == '>') /* &> */
 							break; /* return readtoken1(...) */
 #endif
@@ -13274,9 +13346,11 @@ init(void)
 		setvareq((char*)defoptindvar, VTEXTFIXED);
 
 		setvar0("PPID", utoa(getppid()));
-#if ENABLE_ASH_BASH_COMPAT
+#if ENABLE_ASH_SHLVL_VAR
 		p = lookupvar("SHLVL");
 		setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT);
+#endif
+#if ENABLE_ASH_HOSTNAME_VAR
 		if (!lookupvar("HOSTNAME")) {
 			struct utsname uts;
 			uname(&uts);
diff --git a/shell/hush.c b/shell/hush.c
index 68b838378..be64202dc 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -95,18 +95,6 @@
 //config:	  It does not handle select, aliases, tilde expansion,
 //config:	  &>file and >&file redirection of stdout+stderr.
 //config:
-//config:config HUSH_BASH_COMPAT
-//config:	bool "bash-compatible extensions"
-//config:	default y
-//config:	depends on HUSH || SH_IS_HUSH || BASH_IS_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_INTERACTIVE
 //config:	bool "Interactive mode"
 //config:	default y
@@ -269,6 +257,55 @@
 //config:	default n
 //config:	depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:
+//config:config HUSH_BASH_COMPAT
+//config:	bool "bash-compatible extensions"
+//config:	default y
+//config:	depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:	help
+//config:	  Saying N to this will skip all options about bash-compatible
+//config:	  extensions. This option alone does not add any code.
+//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_SUBSTR_EXPANSION
+//config:	bool "Substring expansion ${var:position:length}"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:	select FEATURE_SH_MATH # no-arithmetic version is not implemented
+//config:
+//config:config HUSH_PATTERN_SUBST
+//config:	bool "Pattern substitution ${var/pattern/replacement}"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:
+//config:config HUSH_SOURCE
+//config:	bool "source builtin"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:	help
+//config:	  Enable source builtin, which is equivalent to . (dot) builtin.
+//config:	  The availabilty of . (dot) builtin is unaffected by this option.
+//config:
+//config:config HUSH_TEST2
+//config:	bool "test construct [[ EXPR ]]"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:	help
+//config:	  The test construct [[ EXPR ]] support in hush is currently
+//config:	  limited to supressing word splitting and filename expansion
+//config:	  within brackets.
+/*            ^^^^^ TODO: update this if new feature gets implemented ^^^^^ */
+//config:
+//config:config HUSH_HOSTNAME_VAR
+//config:	bool "$HOSTNAME variable"
+//config:	default y
+//config:	depends on HUSH_BASH_COMPAT
+//config:
 //config:config MSH
 //config:	bool "msh (deprecated: aliased to hush)"
 //config:	default n
@@ -411,7 +448,7 @@
 #define _SPECIAL_VARS_STR     "_*@$!?#"
 #define SPECIAL_VARS_STR     ("_*@$!?#" + 1)
 #define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3)
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_PATTERN_SUBST
 /* Support / and // replace ops */
 /* Note that // is stored as \ in "encoded" string representation */
 # define VAR_ENCODED_SUBST_OPS      "\\/%#:-=+?"
@@ -572,7 +609,7 @@ struct command {
 	smallint cmd_type;          /* CMD_xxx */
 #define CMD_NORMAL   0
 #define CMD_SUBSHELL 1
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_TEST2
 /* used for "[[ EXPR ]]" */
 # define CMD_SINGLEWORD_NOGLOB 2
 #endif
@@ -947,7 +984,7 @@ 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
+#if ENABLE_HUSH_TEST || ENABLE_HUSH_TEST2
 static int builtin_test(char **argv) FAST_FUNC;
 #endif
 #if ENABLE_HUSH_TRAP
@@ -1044,7 +1081,7 @@ static const struct built_in_command bltins1[] = {
 	BLTIN("set"      , builtin_set     , "Set positional parameters"),
 #endif
 	BLTIN("shift"    , builtin_shift   , "Shift positional parameters"),
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_SOURCE
 	BLTIN("source"   , builtin_source  , NULL),
 #endif
 #if ENABLE_HUSH_TRAP
@@ -1073,9 +1110,9 @@ static const struct built_in_command bltins1[] = {
 static const struct built_in_command bltins2[] = {
 #if ENABLE_HUSH_TEST
 	BLTIN("["        , builtin_test    , NULL),
-# if ENABLE_HUSH_BASH_COMPAT
+#endif
+#if ENABLE_HUSH_TEST2
 	BLTIN("[["       , builtin_test    , NULL),
-# endif
 #endif
 #if ENABLE_HUSH_ECHO
 	BLTIN("echo"     , builtin_echo    , NULL),
@@ -2177,7 +2214,7 @@ static void unset_vars(char **strings)
 	free(strings);
 }
 
-#if ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_BASH_COMPAT || ENABLE_HUSH_READ
+#if ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_HOSTNAME_VAR || ENABLE_HUSH_READ
 static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
 {
 	char *var = xasprintf("%s=%s", name, val);
@@ -3636,7 +3673,7 @@ static int done_word(o_string *word, struct parse_context *ctx)
 						(ctx->ctx_res_w == RES_SNTX));
 				return (ctx->ctx_res_w == RES_SNTX);
 			}
-# if ENABLE_HUSH_BASH_COMPAT
+# if ENABLE_HUSH_TEST2
 			if (strcmp(word->data, "[[") == 0) {
 				command->cmd_type = CMD_SINGLEWORD_NOGLOB;
 			}
@@ -4234,7 +4271,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
 {
 	int ch;
 	char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG;
-# if ENABLE_HUSH_BASH_COMPAT
+# if ENABLE_HUSH_SUBSTR_EXPANSION || ENABLE_HUSH_PATTERN_SUBST
 	char end_char2 = end_ch >> 8;
 # endif
 	end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1);
@@ -4245,7 +4282,11 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
 			syntax_error_unterm_ch(end_ch);
 			return 0;
 		}
-		if (ch == end_ch  IF_HUSH_BASH_COMPAT( || ch == end_char2)) {
+		if (ch == end_ch
+# if ENABLE_HUSH_SUBSTR_EXPANSION || ENABLE_HUSH_PATTERN_SUBST
+			|| ch == end_char2
+# endif
+		) {
 			if (!dbl)
 				break;
 			/* we look for closing )) of $((EXPR)) */
@@ -4398,14 +4439,14 @@ static int parse_dollar(o_string *as_string,
 
 				/* Eat everything until closing '}' (or ':') */
 				end_ch = '}';
-				if (ENABLE_HUSH_BASH_COMPAT
+				if (ENABLE_HUSH_SUBSTR_EXPANSION
 				 && ch == ':'
 				 && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input))
 				) {
 					/* It's ${var:N[:M]} thing */
 					end_ch = '}' * 0x100 + ':';
 				}
-				if (ENABLE_HUSH_BASH_COMPAT
+				if (ENABLE_HUSH_PATTERN_SUBST
 				 && ch == '/'
 				) {
 					/* It's ${var/[/]pattern[/repl]} thing */
@@ -4432,7 +4473,9 @@ static int parse_dollar(o_string *as_string,
 					o_addchr(as_string, last_ch);
 				}
 
-				if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) {
+				if ((ENABLE_HUSH_SUBSTR_EXPANSION || ENABLE_HUSH_PATTERN_SUBST)
+					 && (end_ch & 0xff00)
+				) {
 					/* close the first block: */
 					o_addchr(dest, SPECIAL_VAR_SYMBOL);
 					/* while parsing N from ${var:N[:M]}
@@ -4443,7 +4486,7 @@ static int parse_dollar(o_string *as_string,
 						goto again;
 					}
 					/* got '}' */
-					if (end_ch == '}' * 0x100 + ':') {
+					if (ENABLE_HUSH_SUBSTR_EXPANSION && end_ch == '}' * 0x100 + ':') {
 						/* it's ${var:N} - emulate :999999999 */
 						o_addstr(dest, "999999999");
 					} /* else: it's ${var/[/]pattern} */
@@ -4518,7 +4561,7 @@ static int parse_dollar(o_string *as_string,
 }
 
 #if BB_MMU
-# if ENABLE_HUSH_BASH_COMPAT
+# if ENABLE_HUSH_PATTERN_SUBST
 #define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
 	encode_string(dest, input, dquote_end, process_bkslash)
 # else
@@ -4530,7 +4573,7 @@ static int parse_dollar(o_string *as_string,
 
 #else /* !MMU */
 
-# if ENABLE_HUSH_BASH_COMPAT
+# if ENABLE_HUSH_PATTERN_SUBST
 /* all parameters are needed, no macro tricks */
 # else
 #define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
@@ -4543,7 +4586,7 @@ static int encode_string(o_string *as_string,
 		int dquote_end,
 		int process_bkslash)
 {
-#if !ENABLE_HUSH_BASH_COMPAT
+#if !ENABLE_HUSH_PATTERN_SUBST
 	const int process_bkslash = 1;
 #endif
 	int ch;
@@ -5186,7 +5229,7 @@ static struct pipe *parse_stream(char **pstring,
 /*** Execution routines ***/
 
 /* Expansion can recurse, need forward decls: */
-#if !ENABLE_HUSH_BASH_COMPAT
+#if !ENABLE_HUSH_PATTERN_SUBST
 /* only ${var/pattern/repl} (its pattern part) needs additional mode */
 #define expand_string_to_string(str, do_unbackslash) \
 	expand_string_to_string(str)
@@ -5307,7 +5350,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
  * Returns malloced string.
  * As an optimization, we return NULL if expansion is not needed.
  */
-#if !ENABLE_HUSH_BASH_COMPAT
+#if !ENABLE_HUSH_PATTERN_SUBST
 /* only ${var/pattern/repl} (its pattern part) needs additional mode */
 #define encode_then_expand_string(str, process_bkslash, do_unbackslash) \
 	encode_then_expand_string(str)
@@ -5361,7 +5404,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 }
 #endif
 
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_PATTERN_SUBST
 /* ${var/[/]pattern[/repl]} helpers */
 static char *strstr_pattern(char *val, const char *pattern, int *size)
 {
@@ -5413,7 +5456,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
 	debug_printf_varexp("result:'%s'\n", result);
 	return result;
 }
-#endif
+#endif /* HUSH_PATTERN_SUBST */
 
 /* Helper:
  * Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct.
@@ -5461,7 +5504,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 			if (exp_op == ':') {
 				exp_op = *exp_word++;
 //TODO: try ${var:} and ${var:bogus} in non-bash config
-				if (ENABLE_HUSH_BASH_COMPAT
+				if (ENABLE_HUSH_SUBSTR_EXPANSION
 				 && (!exp_op || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
 				) {
 					/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
@@ -5543,7 +5586,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				}
 			}
 		}
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_PATTERN_SUBST
 		else if (exp_op == '/' || exp_op == '\\') {
 			/* It's ${var/[/]pattern[/repl]} thing.
 			 * Note that in encoded form it has TWO parts:
@@ -5590,9 +5633,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				free(repl);
 			}
 		}
-#endif
+#endif /* HUSH_PATTERN_SUBST */
 		else if (exp_op == ':') {
-#if ENABLE_HUSH_BASH_COMPAT && ENABLE_FEATURE_SH_MATH
+#if ENABLE_HUSH_SUBSTR_EXPANSION && 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>
@@ -5628,7 +5671,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 				}
 				debug_printf_varexp("val:'%s'\n", val);
 			} else
-#endif
+#endif /* HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH */
 			{
 				die_if_script("malformed ${%s:...}", var);
 				val = NULL;
@@ -5837,7 +5880,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 #endif
 		default:
 			val = expand_one_var(&to_be_freed, arg, &p);
- IF_HUSH_TICK(store_val:)
+#if ENABLE_HUSH_TICK
+ store_val:
+#endif
 			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));
@@ -5918,7 +5963,7 @@ static char **expand_strvec_to_strvec(char **argv)
 	return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS);
 }
 
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_TEST2
 static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
 {
 	return expand_variables(argv, EXP_FLAG_SINGLEWORD);
@@ -5933,7 +5978,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
  */
 static char *expand_string_to_string(const char *str, int do_unbackslash)
 {
-#if !ENABLE_HUSH_BASH_COMPAT
+#if !ENABLE_HUSH_PATTERN_SUBST
 	const int do_unbackslash = 1;
 #endif
 	char *argv[2], **list;
@@ -7655,7 +7700,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
 		}
 
 		/* Expand the rest into (possibly) many strings each */
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_TEST2
 		if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
 			argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
 		} else
@@ -8411,7 +8456,7 @@ int hush_main(int argc, char **argv)
 	/* Export PWD */
 	set_pwd_var(/*exp:*/ 1);
 
-#if ENABLE_HUSH_BASH_COMPAT
+#if ENABLE_HUSH_HOSTNAME_VAR
 	/* Set (but not export) HOSTNAME unless already set */
 	if (!get_local_var_value("HOSTNAME")) {
 		struct utsname uts;
@@ -8819,7 +8864,7 @@ static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char *
 	return applet_main_func(argc, argv - argc);
 }
 #endif
-#if ENABLE_HUSH_TEST
+#if ENABLE_HUSH_TEST || ENABLE_HUSH_TEST2
 static int FAST_FUNC builtin_test(char **argv)
 {
 	return run_applet_main(argv, test_main);
-- 
2.11.0

_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to