From 5b2db97703630885b8f4c1ce0371415d1d2501a9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 16 Nov 2009 05:49:36 +0100 Subject: [PATCH] hush: initial stab at brace expansion support Signed-off-by: Denys Vlasenko --- shell/hush.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 235cee9ee..171b73940 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2003,6 +2003,130 @@ static int o_get_last_ptr(o_string *o, int n) 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 @@ static int o_glob(o_string *o, int n) } 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]) { -- 2.25.1