]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - braces.c
Bash-5.2 patch 26: fix typo when specifying readline's custom color prefix
[thirdparty/bash.git] / braces.c
index 0fb9b9d7184babc1756c5fd5cb4b08d6e307bcdc..e91d326ea42d39b3f0cabc9dd0e21d7c597e62a0 100644 (file)
--- a/braces.c
+++ b/braces.c
@@ -1,22 +1,22 @@
 /* braces.c -- code for doing word expansion in curly braces. */
 
-/* Copyright (C) 1987-2003 Free Software Foundation, Inc.
+/* 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 2, or (at your option)
-   any later version.
+   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.
+   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; see the file COPYING.  If not, write to the Free
-   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 /* Stuff in curly braces gets expanded before all other shell expansions. */
 
 #  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),
  */
 
 /* The character which is used to separate arguments. */
-int brace_arg_separator = ',';
-
-#if defined (__P)
-static int brace_gobbler __P((char *, size_t, int *, int));
-static char **expand_amble __P((char *, size_t, int));
-static char **expand_seqterm __P((char *, size_t));
-static char **mkseq __P((int, int, int));
-static char **array_concat __P((char **, char **));
+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 ();
@@ -71,6 +87,18 @@ 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)
@@ -81,17 +109,49 @@ brace_expand (text)
   char *preamble, *postamble, *amble;
   size_t alen;
   char **tack, **result;
-  int i, j, c;
+  int i, j, c, c1;
 
   DECLARE_MBSTATE;
 
   /* Find the text of the preamble. */
   tlen = strlen (text);
   i = 0;
-  c = brace_gobbler (text, tlen, &i, '{');
+#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);
-  strncpy (preamble, text, i);
+  if (i > 0)
+    strncpy (preamble, text, i);
   preamble[i] = '\0';
 
   result = (char **)xmalloc (2 * sizeof (char *));
@@ -126,6 +186,7 @@ brace_expand (text)
          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 ();
            }
@@ -173,6 +234,19 @@ brace_expand (text)
       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);
@@ -187,13 +261,18 @@ brace_expand (text)
 add_tack:
   result = array_concat (result, tack);
   free (amble);
-  strvec_dispose (tack);
+  if (tack != result)
+    strvec_dispose (tack);
 
   postamble = text + i + 1;
 
-  tack = brace_expand (postamble);
-  result = array_concat (result, tack);
-  strvec_dispose (tack);
+  if (postamble && *postamble)
+    {
+      tack = brace_expand (postamble);
+      result = array_concat (result, tack);
+      if (tack != result)
+       strvec_dispose (tack);
+    }
 
   return (result);
 }
@@ -208,11 +287,13 @@ expand_amble (text, tlen, flags)
      size_t tlen;
      int flags;
 {
-  char **result, **partial;
+  char **result, **partial, **tresult;
   char *tem;
   int start, i, c;
 
+#if defined (SHELL)
   DECLARE_MBSTATE;
+#endif
 
   result = (char **)NULL;
 
@@ -226,7 +307,7 @@ expand_amble (text, tlen, flags)
 #else
       tem = (char *)xmalloc (1 + (i - start));
       strncpy (tem, &text[start], (i - start));
-      tem[i- start] = '\0';
+      tem[i - start] = '\0';
 #endif
 
       partial = brace_expand (tem);
@@ -240,7 +321,18 @@ expand_amble (text, tlen, flags)
          lr = strvec_len (result);
          lp = strvec_len (partial);
 
-         result = strvec_resize (result, lp + lr + 1);
+         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];
@@ -249,7 +341,11 @@ expand_amble (text, tlen, flags)
          free (partial);
        }
       free (tem);
+#if defined (SHELL)
       ADVANCE_CHAR (text, tlen, i);
+#else
+      i++;
+#endif
       start = i;
     }
   return (result);
@@ -258,36 +354,113 @@ expand_amble (text, tlen, flags)
 #define ST_BAD 0
 #define ST_INT 1
 #define ST_CHAR        2
+#define ST_ZINT        3
 
 static char **
-mkseq (start, end, type)
-     int start, end, type;
+mkseq (start, end, incr, type, width)
+     intmax_t start, end, incr;
+     int type, width;
 {
-  int n, incr, i;
+  intmax_t n, prevn;
+  int i, nelem;
   char **result, *t;
 
-  n = abs (end - start) + 1;
-  result = strvec_create (n + 1);
+  if (incr == 0)
+    incr = 1;
 
-  incr = (start < end) ? 1 : -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++] = itos (n);
+       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
        {
-         t = (char *)xmalloc (2);
-         t[0] = n;
-         t[1] = '\0';
+         if (t = (char *)malloc (2))
+           {
+             t[0] = n;
+             t[1] = '\0';
+           }
          result[i++] = t;
        }
-      if (n == end)
+
+      /* 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);
 
@@ -301,17 +474,18 @@ expand_seqterm (text, tlen)
      size_t tlen;
 {
   char *t, *lhs, *rhs;
-  int i, lhs_t, rhs_t, lhs_v, rhs_v;
+  int lhs_t, rhs_t, lhs_l, rhs_l, width;
+  intmax_t lhs_v, rhs_v, incr;
   intmax_t tl, tr;
-  char **result;
+  char **result, *ep, *oep;
 
   t = strstr (text, BRACE_SEQ_SPECIFIER);
   if (t == 0)
     return ((char **)NULL);
 
-  i = t - text;                /* index of start of BRACE_SEQ_SPECIFIER */
-  lhs = substring (text, 0, i);
-  rhs = substring (text, i + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen);
+  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)
     {
@@ -324,8 +498,40 @@ expand_seqterm (text, tlen)
      sides have to match. */
   lhs_t = (legal_number (lhs, &tl)) ? ST_INT :
                ((ISALPHA (lhs[0]) && lhs[1] == 0) ?  ST_CHAR : ST_BAD);
-  rhs_t = (legal_number (rhs, &tr)) ? ST_INT :
-               ((ISALPHA (rhs[0]) && rhs[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)
     {
@@ -340,16 +546,34 @@ expand_seqterm (text, tlen)
   
   if (lhs_t == ST_CHAR)
     {
-      lhs_v = lhs[0];
-      rhs_v = rhs[0];
+      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, lhs_t);
+  result = mkseq (lhs_v, rhs_v, incr, lhs_t, width);
 
   free (lhs);
   free (rhs);
@@ -361,6 +585,11 @@ expand_seqterm (text, tlen)
    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;
@@ -368,7 +597,7 @@ brace_gobbler (text, tlen, indx, satisfy)
      int *indx;
      int satisfy;
 {
-  register int i, c, quoted, level, pass_next;
+  register int i, c, quoted, level, commas, pass_next;
 #if defined (SHELL)
   int si;
   char *t;
@@ -376,6 +605,11 @@ brace_gobbler (text, tlen, indx, satisfy)
   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])
@@ -383,7 +617,11 @@ brace_gobbler (text, tlen, indx, satisfy)
       if (pass_next)
        {
          pass_next = 0;
+#if defined (SHELL)
          ADVANCE_CHAR (text, tlen, i);
+#else
+         i++;
+#endif
          continue;
        }
 
@@ -402,6 +640,8 @@ brace_gobbler (text, tlen, indx, satisfy)
        {
          pass_next = 1;
          i++;
+         if (quoted == 0)
+           level++;
          continue;
        }
 #endif
@@ -410,7 +650,16 @@ brace_gobbler (text, tlen, indx, satisfy)
        {
          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;
        }
 
@@ -422,11 +671,12 @@ brace_gobbler (text, tlen, indx, satisfy)
        }
 
 #if defined (SHELL)
-      /* Pass new-style command substitutions through unchanged. */
-      if (c == '$' && text[i+1] == '(')                        /* ) */
+      /* 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);
+         t = extract_command_subst (text, &si, 0);
          i = si;
          free (t);
          i++;
@@ -434,7 +684,7 @@ brace_gobbler (text, tlen, indx, satisfy)
        }
 #endif
 
-      if (c == satisfy && level == 0 && quoted == 0)
+      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
@@ -454,8 +704,19 @@ brace_gobbler (text, tlen, indx, satisfy)
        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;
@@ -475,15 +736,29 @@ array_concat (arr1, arr2)
   register char **result;
 
   if (arr1 == 0)
-    return (strvec_copy (arr2));
+    return (arr2);             /* XXX - see if we can get away without copying? */
 
   if (arr2 == 0)
-    return (strvec_copy (arr1));
+    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 **)xmalloc ((1 + (len1 * len2)) * sizeof (char *));
+  result = (char **)malloc ((1 + (len1 * len2)) * sizeof (char *));
+  if (result == 0)
+    return (result);
 
   len = 0;
   for (i = 0; i < len1; i++)
@@ -508,20 +783,29 @@ array_concat (arr1, arr2)
 #if defined (TEST)
 #include <stdio.h>
 
-fatal_error (format, arg1, arg2)
-     char *format, *arg1, *arg2;
+void *
+xmalloc(n)
+     size_t n;
+{
+  return (malloc (n));
+}
+
+void *
+xrealloc(p, n)
+     void *p;
+     size_t n;
 {
-  report_error (format, arg1, arg2);
-  exit (1);
+  return (realloc (p, n));
 }
 
-report_error (format, arg1, arg2)
+int
+internal_error (format, arg1, arg2)
      char *format, *arg1, *arg2;
 {
   fprintf (stderr, format, arg1, arg2);
   fprintf (stderr, "\n");
 }
-
+      
 main ()
 {
   char example[256];
@@ -545,7 +829,7 @@ main ()
       for (i = 0; result[i]; i++)
        printf ("%s\n", result[i]);
 
-      free_array (result);
+      strvec_dispose (result);
     }
 }
 \f