diff options
Diffstat (limited to 'braces.c')
-rw-r--r-- | braces.c | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/braces.c b/braces.c new file mode 100644 index 0000000..e91d326 --- /dev/null +++ b/braces.c @@ -0,0 +1,843 @@ +/* braces.c -- code for doing word expansion in curly braces. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Stuff in curly braces gets expanded before all other shell expansions. */ + +#include "config.h" + +#if defined (BRACE_EXPANSION) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include <errno.h> + +#include "bashansi.h" +#include "bashintl.h" + +#if defined (SHELL) +# include "shell.h" +#else +# if defined (TEST) +typedef char *WORD_DESC; +typedef char **WORD_LIST; +#define _(X) X +# endif /* TEST */ +#endif /* SHELL */ + +#include "typemax.h" /* INTMAX_MIN, INTMAX_MAX */ +#include "general.h" +#include "shmbutil.h" +#include "chartypes.h" + +#ifndef errno +extern int errno; +#endif + +#define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n') + +#define BRACE_SEQ_SPECIFIER ".." + +extern int asprintf PARAMS((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); + +/* Basic idea: + + Segregate the text into 3 sections: preamble (stuff before an open brace), + postamble (stuff after the matching close brace) and amble (stuff after + preamble, and before postamble). Expand amble, and then tack on the + expansions to preamble. Expand postamble, and tack on the expansions to + the result so far. + */ + +/* The character which is used to separate arguments. */ +static const int brace_arg_separator = ','; + +#if defined (PARAMS) +static int brace_gobbler PARAMS((char *, size_t, int *, int)); +static char **expand_amble PARAMS((char *, size_t, int)); +static char **expand_seqterm PARAMS((char *, size_t)); +static char **mkseq PARAMS((intmax_t, intmax_t, intmax_t, int, int)); +static char **array_concat PARAMS((char **, char **)); +#else +static int brace_gobbler (); +static char **expand_amble (); +static char **expand_seqterm (); +static char **mkseq(); +static char **array_concat (); +#endif + +#if 0 +static void +dump_result (a) + char **a; +{ + int i; + + for (i = 0; a[i]; i++) + printf ("dump_result: a[%d] = -%s-\n", i, a[i]); +} +#endif + +/* Return an array of strings; the brace expansion of TEXT. */ +char ** +brace_expand (text) + char *text; +{ + register int start; + size_t tlen; + char *preamble, *postamble, *amble; + size_t alen; + char **tack, **result; + int i, j, c, c1; + + DECLARE_MBSTATE; + + /* Find the text of the preamble. */ + tlen = strlen (text); + i = 0; +#if defined (CSH_BRACE_COMPAT) + c = brace_gobbler (text, tlen, &i, '{'); /* } */ +#else + /* Make sure that when we exit this loop, c == 0 or text[i] begins a + valid brace expansion sequence. */ + do + { + c = brace_gobbler (text, tlen, &i, '{'); /* } */ + c1 = c; + /* Verify that c begins a valid brace expansion word. If it doesn't, we + go on. Loop stops when there are no more open braces in the word. */ + if (c) + { + start = j = i + 1; /* { */ + c = brace_gobbler (text, tlen, &j, '}'); + if (c == 0) /* it's not */ + { + i++; + c = c1; + continue; + } + else /* it is */ + { + c = c1; + break; + } + } + else + break; + } + while (c); +#endif /* !CSH_BRACE_COMPAT */ + + preamble = (char *)xmalloc (i + 1); + if (i > 0) + strncpy (preamble, text, i); + preamble[i] = '\0'; + + result = (char **)xmalloc (2 * sizeof (char *)); + result[0] = preamble; + result[1] = (char *)NULL; + + /* Special case. If we never found an exciting character, then + the preamble is all of the text, so just return that. */ + if (c != '{') + return (result); + + /* Find the amble. This is the stuff inside this set of braces. */ + start = ++i; + c = brace_gobbler (text, tlen, &i, '}'); + + /* What if there isn't a matching close brace? */ + if (c == 0) + { +#if defined (NOTDEF) + /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START + and I, then this should be an error. Otherwise, it isn't. */ + j = start; + while (j < i) + { + if (text[j] == '\\') + { + j++; + ADVANCE_CHAR (text, tlen, j); + continue; + } + + if (text[j] == brace_arg_separator) + { /* { */ + strvec_dispose (result); + set_exit_status (EXECUTION_FAILURE); + report_error ("no closing `%c' in %s", '}', text); + throw_to_top_level (); + } + ADVANCE_CHAR (text, tlen, j); + } +#endif + free (preamble); /* Same as result[0]; see initialization. */ + result[0] = savestring (text); + return (result); + } + +#if defined (SHELL) + amble = substring (text, start, i); + alen = i - start; +#else + amble = (char *)xmalloc (1 + (i - start)); + strncpy (amble, &text[start], (i - start)); + alen = i - start; + amble[alen] = '\0'; +#endif + +#if defined (SHELL) + INITIALIZE_MBSTATE; + + /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then + just return without doing any expansion. */ + j = 0; + while (amble[j]) + { + if (amble[j] == '\\') + { + j++; + ADVANCE_CHAR (amble, alen, j); + continue; + } + + if (amble[j] == brace_arg_separator) + break; + + ADVANCE_CHAR (amble, alen, j); + } + + if (amble[j] == 0) + { + tack = expand_seqterm (amble, alen); + if (tack) + goto add_tack; + else if (text[i + 1]) + { + /* If the sequence expansion fails (e.g., because the integers + overflow), but there is more in the string, try and process + the rest of the string, which may contain additional brace + expansions. Treat the unexpanded sequence term as a simple + string (including the braces). */ + tack = strvec_create (2); + tack[0] = savestring (text+start-1); + tack[0][i-start+2] = '\0'; + tack[1] = (char *)0; + goto add_tack; + } + else + { + free (amble); + free (preamble); + result[0] = savestring (text); + return (result); + } + } +#endif /* SHELL */ + + tack = expand_amble (amble, alen, 0); +add_tack: + result = array_concat (result, tack); + free (amble); + if (tack != result) + strvec_dispose (tack); + + postamble = text + i + 1; + + if (postamble && *postamble) + { + tack = brace_expand (postamble); + result = array_concat (result, tack); + if (tack != result) + strvec_dispose (tack); + } + + return (result); +} + +/* Expand the text found inside of braces. We simply try to split the + text at BRACE_ARG_SEPARATORs into separate strings. We then brace + expand each slot which needs it, until there are no more slots which + need it. */ +static char ** +expand_amble (text, tlen, flags) + char *text; + size_t tlen; + int flags; +{ + char **result, **partial, **tresult; + char *tem; + int start, i, c; + +#if defined (SHELL) + DECLARE_MBSTATE; +#endif + + result = (char **)NULL; + + start = i = 0; + c = 1; + while (c) + { + c = brace_gobbler (text, tlen, &i, brace_arg_separator); +#if defined (SHELL) + tem = substring (text, start, i); +#else + tem = (char *)xmalloc (1 + (i - start)); + strncpy (tem, &text[start], (i - start)); + tem[i - start] = '\0'; +#endif + + partial = brace_expand (tem); + + if (!result) + result = partial; + else + { + register int lr, lp, j; + + lr = strvec_len (result); + lp = strvec_len (partial); + + tresult = strvec_mresize (result, lp + lr + 1); + if (tresult == 0) + { + internal_error (_("brace expansion: cannot allocate memory for %s"), tem); + free (tem); + strvec_dispose (partial); + strvec_dispose (result); + result = (char **)NULL; + return result; + } + else + result = tresult; + + for (j = 0; j < lp; j++) + result[lr + j] = partial[j]; + + result[lr + j] = (char *)NULL; + free (partial); + } + free (tem); +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + start = i; + } + return (result); +} + +#define ST_BAD 0 +#define ST_INT 1 +#define ST_CHAR 2 +#define ST_ZINT 3 + +static char ** +mkseq (start, end, incr, type, width) + intmax_t start, end, incr; + int type, width; +{ + intmax_t n, prevn; + int i, nelem; + char **result, *t; + + if (incr == 0) + incr = 1; + + if (start > end && incr > 0) + incr = -incr; + else if (start < end && incr < 0) + { + if (incr == INTMAX_MIN) /* Don't use -INTMAX_MIN */ + return ((char **)NULL); + incr = -incr; + } + + /* Check that end-start will not overflow INTMAX_MIN, INTMAX_MAX. The +3 + and -2, not strictly necessary, are there because of the way the number + of elements and value passed to strvec_create() are calculated below. */ + if (SUBOVERFLOW (end, start, INTMAX_MIN+3, INTMAX_MAX-2)) + return ((char **)NULL); + + prevn = sh_imaxabs (end - start); + /* Need to check this way in case INT_MAX == INTMAX_MAX */ + if (INT_MAX == INTMAX_MAX && (ADDOVERFLOW (prevn, 2, INT_MIN, INT_MAX))) + return ((char **)NULL); + /* Make sure the assignment to nelem below doesn't end up <= 0 due to + intmax_t overflow */ + else if (ADDOVERFLOW ((prevn/sh_imaxabs(incr)), 1, INTMAX_MIN, INTMAX_MAX)) + return ((char **)NULL); + + /* XXX - TOFIX: potentially allocating a lot of extra memory if + imaxabs(incr) != 1 */ + /* Instead of a simple nelem = prevn + 1, something like: + nelem = (prevn / imaxabs(incr)) + 1; + would work */ + if ((prevn / sh_imaxabs (incr)) > INT_MAX - 3) /* check int overflow */ + return ((char **)NULL); + nelem = (prevn / sh_imaxabs(incr)) + 1; + result = strvec_mcreate (nelem + 1); + if (result == 0) + { + internal_error (_("brace expansion: failed to allocate memory for %u elements"), (unsigned int)nelem); + return ((char **)NULL); + } + + /* Make sure we go through the loop at least once, so {3..3} prints `3' */ + i = 0; + n = start; + do + { +#if defined (SHELL) + if (ISINTERRUPT) + { + result[i] = (char *)NULL; + strvec_dispose (result); + result = (char **)NULL; + } + QUIT; +#endif + if (type == ST_INT) + result[i++] = t = itos (n); + else if (type == ST_ZINT) + { + int len, arg; + arg = n; + len = asprintf (&t, "%0*d", width, arg); + result[i++] = t; + } + else + { + if (t = (char *)malloc (2)) + { + t[0] = n; + t[1] = '\0'; + } + result[i++] = t; + } + + /* We failed to allocate memory for this number, so we bail. */ + if (t == 0) + { + char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1]; + + /* Easier to do this than mess around with various intmax_t printf + formats (%ld? %lld? %jd?) and PRIdMAX. */ + p = inttostr (n, lbuf, sizeof (lbuf)); + internal_error (_("brace expansion: failed to allocate memory for `%s'"), p); + strvec_dispose (result); + return ((char **)NULL); + } + + /* Handle overflow and underflow of n+incr */ + if (ADDOVERFLOW (n, incr, INTMAX_MIN, INTMAX_MAX)) + break; + + n += incr; + + if ((incr < 0 && n < end) || (incr > 0 && n > end)) + break; + } + while (1); + + result[i] = (char *)0; + return (result); +} + +static char ** +expand_seqterm (text, tlen) + char *text; + size_t tlen; +{ + char *t, *lhs, *rhs; + int lhs_t, rhs_t, lhs_l, rhs_l, width; + intmax_t lhs_v, rhs_v, incr; + intmax_t tl, tr; + char **result, *ep, *oep; + + t = strstr (text, BRACE_SEQ_SPECIFIER); + if (t == 0) + return ((char **)NULL); + + lhs_l = t - text; /* index of start of BRACE_SEQ_SPECIFIER */ + lhs = substring (text, 0, lhs_l); + rhs = substring (text, lhs_l + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen); + + if (lhs[0] == 0 || rhs[0] == 0) + { + free (lhs); + free (rhs); + return ((char **)NULL); + } + + /* Now figure out whether LHS and RHS are integers or letters. Both + sides have to match. */ + lhs_t = (legal_number (lhs, &tl)) ? ST_INT : + ((ISALPHA (lhs[0]) && lhs[1] == 0) ? ST_CHAR : ST_BAD); + + /* Decide on rhs and whether or not it looks like the user specified + an increment */ + ep = 0; + if (ISDIGIT (rhs[0]) || ((rhs[0] == '+' || rhs[0] == '-') && ISDIGIT (rhs[1]))) + { + rhs_t = ST_INT; + errno = 0; + tr = strtoimax (rhs, &ep, 10); + if (errno == ERANGE || (ep && *ep != 0 && *ep != '.')) + rhs_t = ST_BAD; /* invalid */ + } + else if (ISALPHA (rhs[0]) && (rhs[1] == 0 || rhs[1] == '.')) + { + rhs_t = ST_CHAR; + ep = rhs + 1; + } + else + { + rhs_t = ST_BAD; + ep = 0; + } + + incr = 1; + if (rhs_t != ST_BAD) + { + oep = ep; + errno = 0; + if (ep && *ep == '.' && ep[1] == '.' && ep[2]) + incr = strtoimax (ep + 2, &ep, 10); + if (*ep != 0 || errno == ERANGE) + rhs_t = ST_BAD; /* invalid incr or overflow */ + tlen -= ep - oep; + } + + if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD) + { + free (lhs); + free (rhs); + return ((char **)NULL); + } + + /* OK, we have something. It's either a sequence of integers, ascending + or descending, or a sequence or letters, ditto. Generate the sequence, + put it into a string vector, and return it. */ + + if (lhs_t == ST_CHAR) + { + lhs_v = (unsigned char)lhs[0]; + rhs_v = (unsigned char)rhs[0]; + width = 1; + } + else + { + lhs_v = tl; /* integer truncation */ + rhs_v = tr; + + /* Decide whether or not the terms need zero-padding */ + rhs_l = tlen - lhs_l - sizeof (BRACE_SEQ_SPECIFIER) + 1; + width = 0; + if (lhs_l > 1 && lhs[0] == '0') + width = lhs_l, lhs_t = ST_ZINT; + if (lhs_l > 2 && lhs[0] == '-' && lhs[1] == '0') + width = lhs_l, lhs_t = ST_ZINT; + if (rhs_l > 1 && rhs[0] == '0' && width < rhs_l) + width = rhs_l, lhs_t = ST_ZINT; + if (rhs_l > 2 && rhs[0] == '-' && rhs[1] == '0' && width < rhs_l) + width = rhs_l, lhs_t = ST_ZINT; + + if (width < lhs_l && lhs_t == ST_ZINT) + width = lhs_l; + if (width < rhs_l && lhs_t == ST_ZINT) + width = rhs_l; + } + + result = mkseq (lhs_v, rhs_v, incr, lhs_t, width); + + free (lhs); + free (rhs); + + return (result); +} + +/* Start at INDEX, and skip characters in TEXT. Set INDEX to the + index of the character matching SATISFY. This understands about + quoting. Return the character that caused us to stop searching; + this is either the same as SATISFY, or 0. */ +/* If SATISFY is `}', we are looking for a brace expression, so we + should enforce the rules that govern valid brace expansions: + 1) to count as an arg separator, a comma or `..' has to be outside + an inner set of braces. +*/ +static int +brace_gobbler (text, tlen, indx, satisfy) + char *text; + size_t tlen; + int *indx; + int satisfy; +{ + register int i, c, quoted, level, commas, pass_next; +#if defined (SHELL) + int si; + char *t; +#endif + DECLARE_MBSTATE; + + level = quoted = pass_next = 0; +#if defined (CSH_BRACE_COMPAT) + commas = 1; +#else + commas = (satisfy == '}') ? 0 : 1; +#endif + + i = *indx; + while (c = text[i]) + { + if (pass_next) + { + pass_next = 0; +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + continue; + } + + /* A backslash escapes the next character. This allows backslash to + escape the quote character in a double-quoted string. */ + if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`')) + { + pass_next = 1; + i++; + continue; + } + +#if defined (SHELL) + /* If compiling for the shell, treat ${...} like \{...} */ + if (c == '$' && text[i+1] == '{' && quoted != '\'') /* } */ + { + pass_next = 1; + i++; + if (quoted == 0) + level++; + continue; + } +#endif + + if (quoted) + { + if (c == quoted) + quoted = 0; +#if defined (SHELL) + /* The shell allows quoted command substitutions */ + if (quoted == '"' && c == '$' && text[i+1] == '(') /*)*/ + goto comsub; +#endif +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + continue; + } + + if (c == '"' || c == '\'' || c == '`') + { + quoted = c; + i++; + continue; + } + +#if defined (SHELL) + /* Pass new-style command and process substitutions through unchanged. */ + if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */ + { +comsub: + si = i + 2; + t = extract_command_subst (text, &si, 0); + i = si; + free (t); + i++; + continue; + } +#endif + + if (c == satisfy && level == 0 && quoted == 0 && commas > 0) + { + /* We ignore an open brace surrounded by whitespace, and also + an open brace followed immediately by a close brace preceded + by whitespace. */ + if (c == '{' && + ((!i || brace_whitespace (text[i - 1])) && + (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) + { + i++; + continue; + } + + break; + } + + if (c == '{') + level++; + else if (c == '}' && level) + level--; +#if !defined (CSH_BRACE_COMPAT) + else if (satisfy == '}' && c == brace_arg_separator && level == 0) + commas++; + else if (satisfy == '}' && STREQN (text+i, BRACE_SEQ_SPECIFIER, 2) && + text[i+2] != satisfy && level == 0) + commas++; +#endif + +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + } + + *indx = i; + return (c); +} + +/* Return a new array of strings which is the result of appending each + string in ARR2 to each string in ARR1. The resultant array is + len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) + are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 + is returned. */ +static char ** +array_concat (arr1, arr2) + char **arr1, **arr2; +{ + register int i, j, len, len1, len2; + register char **result; + + if (arr1 == 0) + return (arr2); /* XXX - see if we can get away without copying? */ + + if (arr2 == 0) + return (arr1); /* XXX - caller expects us to free arr1 */ + + /* We can only short-circuit if the array consists of a single null element; + otherwise we need to replicate the contents of the other array and + prefix (or append, below) an empty element to each one. */ + if (arr1[0] && arr1[0][0] == 0 && arr1[1] == 0) + { + strvec_dispose (arr1); + return (arr2); /* XXX - use flags to see if we can avoid copying here */ + } + + if (arr2[0] && arr2[0][0] == 0 && arr2[1] == 0) + return (arr1); /* XXX - rather than copying and freeing it */ + + len1 = strvec_len (arr1); + len2 = strvec_len (arr2); + + result = (char **)malloc ((1 + (len1 * len2)) * sizeof (char *)); + if (result == 0) + return (result); + + len = 0; + for (i = 0; i < len1; i++) + { + int strlen_1 = strlen (arr1[i]); + + for (j = 0; j < len2; j++) + { + result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); + strcpy (result[len], arr1[i]); + strcpy (result[len] + strlen_1, arr2[j]); + len++; + } + free (arr1[i]); + } + free (arr1); + + result[len] = (char *)NULL; + return (result); +} + +#if defined (TEST) +#include <stdio.h> + +void * +xmalloc(n) + size_t n; +{ + return (malloc (n)); +} + +void * +xrealloc(p, n) + void *p; + size_t n; +{ + return (realloc (p, n)); +} + +int +internal_error (format, arg1, arg2) + char *format, *arg1, *arg2; +{ + fprintf (stderr, format, arg1, arg2); + fprintf (stderr, "\n"); +} + +main () +{ + char example[256]; + + for (;;) + { + char **result; + int i; + + fprintf (stderr, "brace_expand> "); + + if ((!fgets (example, 256, stdin)) || + (strncmp (example, "quit", 4) == 0)) + break; + + if (strlen (example)) + example[strlen (example) - 1] = '\0'; + + result = brace_expand (example); + + for (i = 0; result[i]; i++) + printf ("%s\n", result[i]); + + strvec_dispose (result); + } +} + +/* + * Local variables: + * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" + * end: + */ + +#endif /* TEST */ +#endif /* BRACE_EXPANSION */ |