hush: initial stab at brace expansion support

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 235cee9..171b739 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2003,6 +2003,130 @@
 	return ((int)(ptrdiff_t)list[n-1]) + string_start;
 }
 
+#undef HUSH_BRACE_EXP
+/* 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.
+ *
+ * This code needs corresponding quoting on variable expansion side.
+ * Currently, "a='{q,w}'; echo $a" erroneously expands braces in $a
+ */
+#ifdef HUSH_BRACE_EXP
+/* 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;
+}
+static int glob_brace(const char *pattern, int flags, glob_t *pglob)
+{
+	const char *begin;
+	char *alt_start;
+	const char *p;
+	const char *next;
+	const char *rest;
+	size_t rest_len;
+	char *onealt;
+
+	debug_printf_glob("glob_brace('%s')\n", pattern);
+
+	begin = pattern;
+	while (1) {
+		if (*begin == '\0')
+			goto do_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 do_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 do_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 */
+	onealt = alloca(strlen(pattern));
+	/* We know the prefix for all sub-patterns */
+	alt_start = mempcpy(onealt, pattern, begin - 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) {
+		int result;
+
+		/* Construct the new glob expression */
+		memcpy(mempcpy(alt_start, p, next - p), rest, rest_len);
+
+		result = glob_brace(onealt, flags, pglob);
+		/* If we got an error, return it */
+		if (result && result != GLOB_NOMATCH)
+			return result;
+
+		flags |= GLOB_APPEND;
+
+		/*{*/ if (*next == '}') {
+			/* We saw the last entry */
+			break;
+		}
+		p = next + 1;
+		next = next_brace_sub(next);
+	}
+
+	/* We found some entries */
+	return 0;
+
+ do_glob:
+	return glob(pattern, flags, NULL, pglob);
+}
+#endif
+
 /* o_glob performs globbing on last list[], saving each result
  * as a new list[]. */
 static int o_glob(o_string *o, int n)
@@ -2024,17 +2148,22 @@
 	}
 
 	memset(&globdata, 0, sizeof(globdata));
-//TODO: can use GLOB_BRACE | GLOB_TILDE here:
+#ifdef HUSH_BRACE_EXP
+	gr = glob_brace(pattern, GLOB_NOCHECK, &globdata);
+	debug_printf_glob("glob_brace('%s'):%d\n", pattern, gr);
+#else
 	gr = glob(pattern, 0, NULL, &globdata);
 	debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+#endif
 	if (gr == GLOB_NOSPACE)
-		bb_error_msg_and_die("out of memory during glob");
+		bb_error_msg_and_die(bb_msg_memory_exhausted);
 	if (gr == GLOB_NOMATCH) {
 		globfree(&globdata);
 		goto literal;
 	}
-	if (gr != 0) { /* GLOB_ABORTED ? */
-		/* TODO: testcase for bad glob pattern behavior */
+	if (gr != 0) {
+		/* GLOB_ABORTED? Only happens with GLOB_ERR flag,
+		 * but we didn't specify it. Paranoia again. */
 		bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
 	}
 	if (globdata.gl_pathv && globdata.gl_pathv[0]) {