hush: initial stab at brace expansion support
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 16 Nov 2009 04:49:36 +0000 (05:49 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 16 Nov 2009 04:49:36 +0000 (05:49 +0100)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/hush.c

index 235cee9eea45c9fc316f7e4b02937f2322c8a9ee..171b7394026eb712ad4e53441416448bb8cd570f 100644 (file)
@@ -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]) {