]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - general.c
Bash-5.0 patch 4: the wait builtin without arguments only waits for known children...
[thirdparty/bash.git] / general.c
index 68685d6ca1856686ea51f2b3329577d7bd4b7b14..9542963aa2d04f9f2465da119c89e8dfb526c80e 100644 (file)
--- a/general.c
+++ b/general.c
@@ -1,28 +1,27 @@
 /* general.c -- Stuff that is used by all files. */
 
-/* Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992
-   Free Software Foundation, Inc.
+/* Copyright (C) 1987-2016 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, 675 Mass Ave, Cambridge, MA 02139, USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #include "bashtypes.h"
-#ifndef _MINIX
+#if defined (HAVE_SYS_PARAM_H)
 #  include <sys/param.h>
 #endif
 #include "posixstat.h"
 #include "filecntl.h"
 #include "bashansi.h"
 #include <stdio.h>
-#include <ctype.h>
+#include "chartypes.h"
 #include <errno.h>
 
+#include "bashintl.h"
+
 #include "shell.h"
-#include <tilde/tilde.h>
+#include "parser.h"
+#include "flags.h"
+#include "findcmd.h"
+#include "test.h"
+#include "trap.h"
 
-#if defined (TIME_WITH_SYS_TIME)
-#  include <sys/time.h>
-#  include <time.h>
-#else
-#  if defined (HAVE_SYS_TIME_H)
-#    include <sys/time.h>
-#  else
-#    include <time.h>
-#  endif
+#include "builtins/common.h"
+
+#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
 #endif
 
-#include <sys/times.h>
-#include "maxpath.h"
+#include <tilde/tilde.h>
 
 #if !defined (errno)
 extern int errno;
 #endif /* !errno */
 
-#ifndef to_upper
-#  define to_upper(c) (islower(c) ? toupper(c) : (c))
-#  define to_lower(c) (isupper(c) ? tolower(c) : (c))
+#ifdef __CYGWIN__
+#  include <sys/cygwin.h>
 #endif
 
-extern int interrupt_immediately;
-extern int interactive_comments;
+static char *bash_special_tilde_expansions __P((char *));
+static int unquoted_tilde_word __P((const char *));
+static void initialize_group_array __P((void));
 
 /* A standard error message to use when getcwd() returns NULL. */
-char *bash_getcwd_errstr = "getcwd: cannot access parent directories";
+const char * const bash_getcwd_errstr = N_("getcwd: cannot access parent directories");
+
+/* Do whatever is necessary to initialize `Posix mode'.  This currently
+   modifies the following variables which are controlled via shopt:
+      interactive_comments
+      source_uses_path
+      expand_aliases
+      inherit_errexit
+      print_shift_error
+
+   and the following variables which cannot be user-modified:
+
+      source_searches_cwd
+
+  If we add to the first list, we need to change the table and functions
+  below */
+
+static struct {
+  int *posix_mode_var;
+} posix_vars[] = 
+{
+  &interactive_comments,
+  &source_uses_path,
+  &expand_aliases,
+  &inherit_errexit,
+  &print_shift_error,
+  0
+};
 
-/* Do whatever is necessary to initialize `Posix mode'. */
 void
 posix_initialize (on)
      int on;
 {
-  interactive_comments = on != 0;
+  /* Things that should be turned on when posix mode is enabled. */
+  if (on != 0)
+    {
+      interactive_comments = source_uses_path = expand_aliases = 1;
+      inherit_errexit = 1;
+      source_searches_cwd = 0;
+      print_shift_error = 1;
+
+    }
+
+  /* Things that should be turned on when posix mode is disabled. */
+  if (on == 0)
+    {
+      source_searches_cwd = 1;
+      expand_aliases = interactive_shell;
+      print_shift_error = 0;
+    }
+}
+
+int
+num_posix_options ()
+{
+  return ((sizeof (posix_vars) / sizeof (posix_vars[0])) - 1);
+}
+
+char *
+get_posix_options (bitmap)
+     char *bitmap;
+{
+  register int i;
+
+  if (bitmap == 0)
+    bitmap = (char *)xmalloc (num_posix_options ());   /* no trailing NULL */
+  for (i = 0; posix_vars[i].posix_mode_var; i++)
+    bitmap[i] = *(posix_vars[i].posix_mode_var);
+  return bitmap;
+}
+
+void
+set_posix_options (bitmap)
+     const char *bitmap;
+{
+  register int i;
+
+  for (i = 0; posix_vars[i].posix_mode_var; i++)
+    *(posix_vars[i].posix_mode_var) = bitmap[i];
 }
 
 /* **************************************************************** */
@@ -95,13 +165,13 @@ string_to_rlimtype (s)
   neg = 0;
   while (s && *s && whitespace (*s))
     s++;
-  if (*s == '-' || *s == '+')
+  if (s && (*s == '-' || *s == '+'))
     {
       neg = *s == '-';
       s++;
     }
-  for ( ; s && *s && digit (*s); s++)
-    ret = (ret * 10) + digit_value (*s);
+  for ( ; s && *s && DIGIT (*s); s++)
+    ret = (ret * 10) + TODIGIT (*s);
   return (neg ? -ret : ret);
 }
 
@@ -110,107 +180,30 @@ print_rlimtype (n, addnl)
      RLIMTYPE n;
      int addnl;
 {
-  char s[sizeof (RLIMTYPE) * 3 + 1];
-  int len;
+  char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p;
 
-  if (n == 0)
-    {
-      printf ("0%s", addnl ? "\n" : "");
-      return;
-    }
+  p = s + sizeof(s);
+  *--p = '\0';
 
   if (n < 0)
     {
-      putchar ('-');
-      n = -n;
+      do
+       *--p = '0' - n % 10;
+      while ((n /= 10) != 0);
+
+      *--p = '-';
+    }
+  else
+    {
+      do
+       *--p = '0' + n % 10;
+      while ((n /= 10) != 0);
     }
 
-  len = sizeof (RLIMTYPE) * 3 + 1;
-  s[--len] = '\0';
-  for ( ; n != 0; n /= 10)
-    s[--len] = n % 10 + '0';
-  printf ("%s%s", s + len, addnl ? "\n" : "");
+  printf ("%s%s", p, addnl ? "\n" : "");
 }
 #endif /* RLIMTYPE */
 
-#if defined (HAVE_TIMEVAL)
-/* Convert a pointer to a struct timeval to seconds and thousandths of a
-   second, returning the values in *SP and *SFP, respectively.  This does
-   rounding on the fractional part, not just truncation to three places. */
-void
-timeval_to_secs (tvp, sp, sfp)
-     struct timeval *tvp;
-     long *sp;
-     int *sfp;
-{
-  int rest;
-
-  *sp = tvp->tv_sec;
-
-  *sfp = tvp->tv_usec % 1000000;       /* pretty much a no-op */
-  rest = *sfp % 1000;
-  *sfp = (*sfp * 1000) / 1000000;
-  if (rest >= 500)
-    *sfp += 1;
-}
-  
-/* Print the contents of a struct timeval * in a standard way to stdio
-   stream FP.  */
-void
-print_timeval (fp, tvp)
-     FILE *fp;
-     struct timeval *tvp;
-{
-  int minutes, seconds_fraction;
-  long seconds;
-
-  timeval_to_secs (tvp, &seconds, &seconds_fraction);
-
-  minutes = seconds / 60;
-  seconds %= 60;
-
-  fprintf (fp, "%0dm%0ld.%03ds",  minutes, seconds, seconds_fraction);
-}
-#endif /* HAVE_TIMEVAL */
-
-#if defined (HAVE_TIMES)
-void
-clock_t_to_secs (t, sp, sfp)
-     clock_t t;
-     long *sp;
-     int *sfp;
-{
-  static long clk_tck = 0;
-
-  if (clk_tck == 0)
-    clk_tck = get_clk_tck ();
-
-  *sfp = t % clk_tck;
-  *sfp = (*sfp * 1000) / clk_tck;
-
-  *sp = t / clk_tck;
-}
-
-/* Print the time defined by a time_t (returned by the `times' and `time'
-   system calls) in a standard way to stdion stream FP.  This is scaled in
-   terms of HZ, which is what is returned by the `times' call. */
-void
-print_time_in_hz (fp, t)
-     FILE *fp;
-     clock_t t;
-{
-  int minutes, seconds_fraction;
-  long seconds;
-
-  clock_t_to_secs (t, &seconds, &seconds_fraction);
-
-  minutes = seconds / 60;
-  seconds %= 60;
-
-  fprintf (fp, "%0dm%0ld.%03ds",  minutes, seconds, seconds_fraction);
-}
-#endif /* HAVE_TIMES */
-
 /* **************************************************************** */
 /*                                                                 */
 /*                    Input Validation Functions                   */
@@ -220,37 +213,46 @@ print_time_in_hz (fp, t)
 /* Return non-zero if all of the characters in STRING are digits. */
 int
 all_digits (string)
-     char *string;
+     const char *string;
 {
-  while (*string)
-    {
-      if (!digit (*string))
-       return (0);
-      else
-       string++;
-    }
+  register const char *s;
+
+  for (s = string; *s; s++)
+    if (DIGIT (*s) == 0)
+      return (0);
+
   return (1);
 }
 
 /* Return non-zero if the characters pointed to by STRING constitute a
    valid number.  Stuff the converted number into RESULT if RESULT is
-   a non-null pointer to a long. */
+   not null. */
 int
 legal_number (string, result)
-     char *string;
-     long *result;
+     const char *string;
+     intmax_t *result;
 {
-  long value;
+  intmax_t value;
   char *ep;
 
   if (result)
     *result = 0;
 
-  value = strtol (string, &ep, 10);
+  if (string == 0)
+    return 0;
+
+  errno = 0;
+  value = strtoimax (string, &ep, 10);
+  if (errno || ep == string)
+    return 0;  /* errno is set on overflow or underflow */
+
+  /* Skip any trailing whitespace, since strtoimax does not. */
+  while (whitespace (*ep))
+    ep++;
 
   /* If *string is not '\0' but *ep is '\0' on return, the entire string
      is valid. */
-  if (string && *string && *ep == '\0')
+  if (*string && *ep == '\0')
     {
       if (result)
        *result = value;
@@ -268,21 +270,73 @@ legal_number (string, result)
    digit. */
 int
 legal_identifier (name)
-     char *name;
+     const char *name;
 {
-  register char *s;
+  register const char *s;
+  unsigned char c;
 
-  if (!name || !*name || (legal_variable_starter (*name) == 0))
+  if (!name || !(c = *name) || (legal_variable_starter (c) == 0))
     return (0);
 
-  for (s = name + 1; *s; s++)
+  for (s = name + 1; (c = *s) != 0; s++)
     {
-      if (legal_variable_char (*s) == 0)
-        return (0);
+      if (legal_variable_char (c) == 0)
+       return (0);
     }
   return (1);
 }
 
+/* Return 1 if NAME is a valid value that can be assigned to a nameref
+   variable.  FLAGS can be 2, in which case the name is going to be used
+   to create a variable.  Other values are currently unused, but could
+   be used to allow values to be stored and indirectly referenced, but
+   not used in assignments. */
+int
+valid_nameref_value (name, flags)
+     const char *name;
+     int flags;
+{
+  if (name == 0 || *name == 0)
+    return 0;
+
+  /* valid identifier */
+#if defined (ARRAY_VARS)  
+  if (legal_identifier (name) || (flags != 2 && valid_array_reference (name, 0)))
+#else
+  if (legal_identifier (name))
+#endif
+    return 1;
+
+  return 0;
+}
+
+int
+check_selfref (name, value, flags)
+     const char *name;
+     char *value;
+     int flags;
+{
+  char *t;
+
+  if (STREQ (name, value))
+    return 1;
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (value, 0))
+    {
+      t = array_variable_name (value, 0, (char **)NULL, (int *)NULL);
+      if (t && STREQ (name, t))
+       {
+         free (t);
+         return 1;
+       }
+      free (t);
+    }
+#endif
+
+  return 0;    /* not a self reference */
+}
+
 /* Make sure that WORD is a valid shell identifier, i.e.
    does not contain a dollar sign, nor is quoted in any way.  Nor
    does it consist of all digits.  If CHECK_WORD is non-zero,
@@ -295,18 +349,133 @@ check_identifier (word, check_word)
 {
   if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word))
     {
-      internal_error ("`%s': not a valid identifier", word->word);
+      internal_error (_("`%s': not a valid identifier"), word->word);
       return (0);
     }
   else if (check_word && legal_identifier (word->word) == 0)
     {
-      internal_error ("`%s': not a valid identifier", word->word);
+      internal_error (_("`%s': not a valid identifier"), word->word);
       return (0);
     }
   else
     return (1);
 }
 
+/* Return 1 if STRING is a function name that the shell will import from
+   the environment.  Currently we reject attempts to import shell functions
+   containing slashes, beginning with newlines or containing blanks.  In
+   Posix mode, we require that STRING be a valid shell identifier.  Not
+   used yet. */
+int
+importable_function_name (string, len)
+     const char *string;
+     size_t len;
+{
+  if (absolute_program (string))       /* don't allow slash */
+    return 0;
+  if (*string == '\n')                 /* can't start with a newline */
+    return 0;
+  if (shellblank (*string) || shellblank(string[len-1]))
+    return 0;
+  return (posixly_correct ? legal_identifier (string) : 1);
+}
+
+int
+exportable_function_name (string)
+     const char *string;
+{
+  if (absolute_program (string))
+    return 0;
+  if (mbschr (string, '=') != 0)
+    return 0;
+  return 1;
+}
+
+/* Return 1 if STRING comprises a valid alias name.  The shell accepts
+   essentially all characters except those which must be quoted to the
+   parser (which disqualifies them from alias expansion anyway) and `/'. */
+int
+legal_alias_name (string, flags)
+     const char *string;
+     int flags;
+{
+  register const char *s;
+
+  for (s = string; *s; s++)
+    if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/'))
+      return 0;
+  return 1;
+}
+
+/* Returns non-zero if STRING is an assignment statement.  The returned value
+   is the index of the `=' sign.  If FLAGS&1 we are expecting a compound assignment
+   and don't want an array subscript before the `='. */
+int
+assignment (string, flags)
+     const char *string;
+     int flags;
+{
+  register unsigned char c;
+  register int newi, indx;
+
+  c = string[indx = 0];
+
+#if defined (ARRAY_VARS)
+  if ((legal_variable_starter (c) == 0) && ((flags&1) == 0 || c != '[')) /* ] */
+#else
+  if (legal_variable_starter (c) == 0)
+#endif
+    return (0);
+
+  while (c = string[indx])
+    {
+      /* The following is safe.  Note that '=' at the start of a word
+        is not an assignment statement. */
+      if (c == '=')
+       return (indx);
+
+#if defined (ARRAY_VARS)
+      if (c == '[')
+       {
+         newi = skipsubscript (string, indx, (flags & 2) ? 1 : 0);
+         /* XXX - why not check for blank subscripts here, if we do in
+            valid_array_reference? */
+         if (string[newi++] != ']')
+           return (0);
+         if (string[newi] == '+' && string[newi+1] == '=')
+           return (newi + 1);
+         return ((string[newi] == '=') ? newi : 0);
+       }
+#endif /* ARRAY_VARS */
+
+      /* Check for `+=' */
+      if (c == '+' && string[indx+1] == '=')
+       return (indx + 1);
+
+      /* Variable names in assignment statements may contain only letters,
+        digits, and `_'. */
+      if (legal_variable_char (c) == 0)
+       return (0);
+
+      indx++;
+    }
+  return (0);
+}
+
+int
+line_isblank (line)
+     const char *line;
+{
+  register int i;
+
+  if (line == 0)
+    return 0;          /* XXX */
+  for (i = 0; line[i]; i++)
+    if (isblank ((unsigned char)line[i]) == 0)
+      break;
+  return (line[i] == '\0');  
+}
+
 /* **************************************************************** */
 /*                                                                 */
 /*          Functions to manage files and file descriptors         */
@@ -324,31 +493,70 @@ check_identifier (word, check_word)
 #endif /* O_NDELAY */
 
 /* Make sure no-delay mode is not set on file descriptor FD. */
-void
-unset_nodelay_mode (fd)
+int
+sh_unset_nodelay_mode (fd)
      int fd;
 {
-  int flags, set;
+  int flags, bflags;
 
   if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
-    return;
+    return -1;
 
-  set = 0;
+  bflags = 0;
 
   /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present
      and O_NDELAY is defined. */
-  if (flags & O_NONBLOCK)
+#ifdef O_NONBLOCK
+  bflags |= O_NONBLOCK;
+#endif
+
+#ifdef O_NDELAY
+  bflags |= O_NDELAY;
+#endif
+
+  if (flags & bflags)
     {
-      flags &= ~O_NONBLOCK;
-      set++;
+      flags &= ~bflags;
+      return (fcntl (fd, F_SETFL, flags));
     }
 
-  if (set)
-    fcntl (fd, F_SETFL, flags);
+  return 0;
+}
+
+/* Just a wrapper for the define in include/filecntl.h */
+int
+sh_setclexec (fd)
+     int fd;
+{
+  return (SET_CLOSE_ON_EXEC (fd));
 }
 
-  /* There is a bug in the NeXT 2.1 rlogind that causes opens
-     of /dev/tty to fail. */
+/* Return 1 if file descriptor FD is valid; 0 otherwise. */
+int
+sh_validfd (fd)
+     int fd;
+{
+  return (fcntl (fd, F_GETFD, 0) >= 0);
+}
+
+int
+fd_ispipe (fd)
+     int fd;
+{
+  errno = 0;
+  return ((lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE));
+}
+
+/* There is a bug in the NeXT 2.1 rlogind that causes opens
+   of /dev/tty to fail. */
+
+#if defined (__BEOS__)
+/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it
+   into a no-op.  This should probably go away in the future. */
+#  undef O_NONBLOCK
+#  define O_NONBLOCK 0
+#endif /* __BEOS__ */
+
 void
 check_dev_tty ()
 {
@@ -364,7 +572,8 @@ check_dev_tty ()
        return;
       tty_fd = open (tty, O_RDWR|O_NONBLOCK);
     }
-  close (tty_fd);
+  if (tty_fd >= 0)
+    close (tty_fd);
 }
 
 /* Return 1 if PATH1 and PATH2 are the same file.  This is kind of
@@ -372,7 +581,7 @@ check_dev_tty ()
    corresponding to PATH1 and PATH2, respectively. */
 int
 same_file (path1, path2, stp1, stp2)
-     char *path1, *path2;
+     const char *path1, *path2;
      struct stat *stp1, *stp2;
 {
   struct stat st1, st2;
@@ -412,8 +621,8 @@ move_to_high_fd (fd, check_new, maxfd)
       nfds = getdtablesize ();
       if (nfds <= 0)
        nfds = 20;
-      if (nfds > 256)
-       nfds = 256;
+      if (nfds > HIGH_FD_MAX)
+       nfds = HIGH_FD_MAX;             /* reasonable maximum */
     }
   else
     nfds = maxfd;
@@ -422,13 +631,15 @@ move_to_high_fd (fd, check_new, maxfd)
     if (fcntl (nfds, F_GETFD, &ignore) == -1)
       break;
 
-  if (nfds && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1)
+  if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1)
     {
       if (check_new == 0 || fd != fileno (stderr))     /* don't close stderr */
        close (fd);
       return (script_fd);
     }
 
+  /* OK, we didn't find one less than our artificial maximum; return the
+     original file descriptor. */
   return (fd);
 }
  
@@ -437,27 +648,20 @@ move_to_high_fd (fd, check_new, maxfd)
    check up to the first newline, or SAMPLE_LEN, whichever comes first.
    All of the characters must be printable or whitespace. */
 
-#if !defined (isspace)
-#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\f')
-#endif
-
-#if !defined (isprint)
-#define isprint(c) (isletter(c) || digit(c) || ispunct(c))
-#endif
-
 int
 check_binary_file (sample, sample_len)
-     unsigned char *sample;
+     const char *sample;
      int sample_len;
 {
   register int i;
+  unsigned char c;
 
   for (i = 0; i < sample_len; i++)
     {
-      if (sample[i] == '\n')
+      c = sample[i];
+      if (c == '\n')
        return (0);
-
-      if (isspace (sample[i]) == 0 && isprint (sample[i]) == 0)
+      if (c == '\0')
        return (1);
     }
 
@@ -466,225 +670,104 @@ check_binary_file (sample, sample_len)
 
 /* **************************************************************** */
 /*                                                                 */
-/*                 Functions to manipulate pathnames               */
+/*                 Functions to manipulate pipes                   */
 /*                                                                 */
 /* **************************************************************** */
 
-/* Return 1 if PATH corresponds to a directory. */
-static int
-canon_stat (path)
-     char *path;
-{
-  int l;
-  char *s;
-  struct stat sb;
-
-  l = strlen (path);
-  s = xmalloc (l + 3);
-  strcpy (s, path);
-  s[l] = '/';
-  s[l+1] = '.';
-  s[l+2] = '\0';
-  l = stat (s, &sb) == 0 && S_ISDIR (sb.st_mode);
-  free (s);
-  return l;
-}
-
-/* Canonicalize PATH, and return a new path.  The new path differs from PATH
-   in that:
-       Multple `/'s are collapsed to a single `/'.
-       Leading `./'s and trailing `/.'s are removed.
-       Trailing `/'s are removed.
-       Non-leading `../'s and trailing `..'s are handled by removing
-       portions of the path. */
-char *
-canonicalize_pathname (path)
-     char *path;
+int
+sh_openpipe (pv)
+     int *pv;
 {
-  register int i, start;
-  char stub_char;
-  char *result;
-
-  /* The result cannot be larger than the input PATH. */
-  result = savestring (path);
+  int r;
 
-  stub_char = (*path == '/') ? '/' : '.';
-
-  /* Walk along RESULT looking for things to compact. */
-  i = 0;
-  while (1)
-    {
-      if (!result[i])
-       break;
+  if ((r = pipe (pv)) < 0)
+    return r;
 
-      while (result[i] && result[i] != '/')
-       i++;
+  pv[0] = move_to_high_fd (pv[0], 1, 64);
+  pv[1] = move_to_high_fd (pv[1], 1, 64);
 
-      start = i++;
+  return 0;  
+}
 
-      /* If we didn't find any slashes, then there is nothing left to do. */
-      if (!result[start])
-       break;
+int
+sh_closepipe (pv)
+     int *pv;
+{
+  if (pv[0] >= 0)
+    close (pv[0]);
 
-      /* Handle multiple `/'s in a row. */
-      while (result[i] == '/')
-       i++;
+  if (pv[1] >= 0)
+    close (pv[1]);
 
-#if 0
-      if ((start + 1) != i)
-#else
-      /* Leave a leading `//' alone, as POSIX requires. */
-      if ((start + 1) != i && (start != 0 || i != 2))
-#endif
-       {
-         strcpy (result + start + 1, result + i);
-         i = start + 1;
-         /* Make sure that what we have so far corresponds to a directory.
-            If it does not, just punt. */
-         if (*result)
-           {
-             char c;
-             c = result[start];
-             result[start] = '\0';
-             if (canon_stat (result) == 0)
-               {
-                 free (result);
-                 return ((char *)NULL);
-               }
-             result[start] = c;
-           }
-       }
-#if 0
-      /* Handle backslash-quoted `/'. */
-      if (start > 0 && result[start - 1] == '\\')
-       continue;
-#endif
+  pv[0] = pv[1] = -1;
+  return 0;
+}
 
-      /* Check for trailing `/'. */
-      if (start && !result[i])
-       {
-       zero_last:
-         result[--i] = '\0';
-         break;
-       }
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to inspect pathnames                  */
+/*                                                                 */
+/* **************************************************************** */
 
-      /* Check for `../', `./' or trailing `.' by itself. */
-      if (result[i] == '.')
-       {
-         /* Handle trailing `.' by itself. */
-         if (!result[i + 1])
-           goto zero_last;
-
-         /* Handle `./'. */
-         if (result[i + 1] == '/')
-           {
-             strcpy (result + i, result + i + 1);
-             i = (start < 0) ? 0 : start;
-             continue;
-           }
-
-         /* Handle `../' or trailing `..' by itself. */
-         if (result[i + 1] == '.' &&
-             (result[i + 2] == '/' || !result[i + 2]))
-           {
-             /* Make sure that the last component corresponds to a directory
-                before blindly chopping it off. */
-             if (i)
-               {
-                 result[i] = '\0';
-                 if (canon_stat (result) == 0)
-                   {
-                     free (result);
-                     return ((char *)NULL);
-                   }
-                 result[i] = '.';
-               }
-             while (--start > -1 && result[start] != '/');
-             strcpy (result + start + 1, result + i + 2);
-#if 0  /* Unnecessary */
-             if (*result && canon_stat (result) == 0)
-               {
-                 free (result);
-                 return ((char *)NULL);
-               }
-#endif
-             i = (start < 0) ? 0 : start;
-             continue;
-           }
-       }
-    }
+int
+file_exists (fn)
+     const char *fn;
+{
+  struct stat sb;
 
-  if (!*result)
-    {
-      *result = stub_char;
-      result[1] = '\0';
-    }
+  return (stat (fn, &sb) == 0);
+}
 
-#if 1
-  /* Turn `//' into `/' -- XXX experimental */
-  if (result[0] == '/' && result[1] == '/' && result[2] == '\0')
-    result[1] = '\0';
-#endif
+int
+file_isdir (fn)
+     const char *fn;
+{
+  struct stat sb;
 
-  return (result);
+  return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode));
 }
 
-/* Turn STRING (a pathname) into an absolute pathname, assuming that
-   DOT_PATH contains the symbolic location of `.'.  This always
-   returns a new string, even if STRING was an absolute pathname to
-   begin with. */
-char *
-make_absolute (string, dot_path)
-     char *string, *dot_path;
+int
+file_iswdir (fn)
+     const char *fn;
 {
-  char *result;
-  int result_len;
+  return (file_isdir (fn) && sh_eaccess (fn, W_OK) == 0);
+}
 
-  if (dot_path == 0 || *string == '/')
-    result = savestring (string);
-  else
-    {
-      if (dot_path[0])
-       {
-         result_len = strlen (dot_path);
-         result = xmalloc (2 + result_len + strlen (string));
-         strcpy (result, dot_path);
-         if (result[result_len - 1] != '/')
-           {
-             result[result_len++] = '/';
-             result[result_len] = '\0';
-           }
-       }
-      else
-       {
-         result = xmalloc (3 + strlen (string));
-         result[0] = '.'; result[1] = '/'; result[2] = '\0';
-         result_len = 2;
-       }
+/* Return 1 if STRING is "." or "..", optionally followed by a directory
+   separator */
+int
+path_dot_or_dotdot (string)
+     const char *string;
+{
+  if (string == 0 || *string == '\0' || *string != '.')
+    return (0);
 
-      strcpy (result + result_len, string);
-    }
+  /* string[0] == '.' */
+  if (PATHSEP(string[1]) || (string[1] == '.' && PATHSEP(string[2])))
+    return (1);
 
-  return (result);
+  return (0);
 }
 
-/* Return 1 if STRING contains an absolute pathname, else 0. */
+/* Return 1 if STRING contains an absolute pathname, else 0.  Used by `cd'
+   to decide whether or not to look up a directory name in $CDPATH. */
 int
 absolute_pathname (string)
-     char *string;
+     const char *string;
 {
-  if (!string || !*string)
+  if (string == 0 || *string == '\0')
     return (0);
 
-  if (*string == '/')
+  if (ABSPATH(string))
+    return (1);
+
+  if (string[0] == '.' && PATHSEP(string[1]))  /* . and ./ */
+    return (1);
+
+  if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2]))      /* .. and ../ */
     return (1);
 
-  if (*string++ == '.')
-    {
-      if (!*string || *string == '/' ||
-          (*string == '.' && (string[1] == '\0' || string[1] == '/')))
-       return (1);
-    }
   return (0);
 }
 
@@ -693,20 +776,59 @@ absolute_pathname (string)
    up through $PATH. */
 int
 absolute_program (string)
-     char *string;
+     const char *string;
 {
-  return ((char *)strchr (string, '/') != (char *)NULL);
+  return ((char *)mbschr (string, '/') != (char *)NULL);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to manipulate pathnames               */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Turn STRING (a pathname) into an absolute pathname, assuming that
+   DOT_PATH contains the symbolic location of `.'.  This always
+   returns a new string, even if STRING was an absolute pathname to
+   begin with. */
+char *
+make_absolute (string, dot_path)
+     const char *string, *dot_path;
+{
+  char *result;
+
+  if (dot_path == 0 || ABSPATH(string))
+#ifdef __CYGWIN__
+    {
+      char pathbuf[PATH_MAX + 1];
+
+      /* WAS cygwin_conv_to_full_posix_path (string, pathbuf); */
+      cygwin_conv_path (CCP_WIN_A_TO_POSIX, string, pathbuf, PATH_MAX);
+      result = savestring (pathbuf);
+    }
+#else
+    result = savestring (string);
+#endif
+  else
+    result = sh_makepath (dot_path, string, 0);
+
+  return (result);
 }
 
 /* Return the `basename' of the pathname in STRING (the stuff after the
-   last '/').  If STRING is not a full pathname, simply return it. */
+   last '/').  If STRING is `/', just return it. */
 char *
 base_pathname (string)
      char *string;
 {
   char *p;
 
-  if (!absolute_pathname (string))
+#if 0
+  if (absolute_pathname (string) == 0)
+    return (string);
+#endif
+
+  if (string[0] == '/' && string[1] == 0)
     return (string);
 
   p = (char *)strrchr (string, '/');
@@ -721,36 +843,17 @@ char *
 full_pathname (file)
      char *file;
 {
-  char *disposer;
-  char *current_dir;
-  int dlen;
+  char *ret;
 
-  file = (*file == '~') ? bash_tilde_expand (file) : savestring (file);
+  file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file);
 
-  if ((*file == '/') && absolute_pathname (file))
+  if (ABSPATH(file))
     return (file);
 
-  disposer = file;
-
-  /* XXX - this should probably be just PATH_MAX or PATH_MAX + 1 */
-  current_dir = xmalloc (2 + PATH_MAX + strlen (file));
-  if (getcwd (current_dir, PATH_MAX) == 0)
-    {
-      sys_error (bash_getcwd_errstr);
-      free (disposer);
-      free (current_dir);
-      return ((char *)NULL);
-    }
-  dlen = strlen (current_dir);
-  current_dir[dlen++] = '/';
-
-  /* Turn /foo/./bar into /foo/bar. */
-  if (file[0] == '.' && file[1] == '/')
-    file += 2;
+  ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT));
+  free (file);
 
-  strcpy (current_dir + dlen, file);
-  free (disposer);
-  return (current_dir);
+  return (ret);
 }
 
 /* A slightly related function.  Get the prettiest name of this
@@ -779,6 +882,93 @@ polite_directory_format (name)
     return (name);
 }
 
+/* Trim NAME.  If NAME begins with `~/', skip over tilde prefix.  Trim to
+   keep any tilde prefix and PROMPT_DIRTRIM trailing directory components
+   and replace the intervening characters with `...' */
+char *
+trim_pathname (name, maxlen)
+     char *name;
+     int maxlen;
+{
+  int nlen, ndirs;
+  intmax_t nskip;
+  char *nbeg, *nend, *ntail, *v;
+
+  if (name == 0 || (nlen = strlen (name)) == 0)
+    return name;
+  nend = name + nlen;
+
+  v = get_string_value ("PROMPT_DIRTRIM");
+  if (v == 0 || *v == 0)
+    return name;
+  if (legal_number (v, &nskip) == 0 || nskip <= 0)
+    return name;
+
+  /* Skip over tilde prefix */
+  nbeg = name;
+  if (name[0] == '~')
+    for (nbeg = name; *nbeg; nbeg++)
+      if (*nbeg == '/')
+       {
+         nbeg++;
+         break;
+       }
+  if (*nbeg == 0)
+    return name;
+
+  for (ndirs = 0, ntail = nbeg; *ntail; ntail++)
+    if (*ntail == '/')
+      ndirs++;
+  if (ndirs < nskip)
+    return name;
+
+  for (ntail = (*nend == '/') ? nend : nend - 1; ntail > nbeg; ntail--)
+    {
+      if (*ntail == '/')
+       nskip--;
+      if (nskip == 0)
+       break;
+    }
+  if (ntail == nbeg)
+    return name;
+
+  /* Now we want to return name[0..nbeg]+"..."+ntail, modifying name in place */
+  nlen = ntail - nbeg;
+  if (nlen <= 3)
+    return name;
+
+  *nbeg++ = '.';
+  *nbeg++ = '.';
+  *nbeg++ = '.';
+
+  nlen = nend - ntail;
+  memmove (nbeg, ntail, nlen);
+  nbeg[nlen] = '\0';
+
+  return name;
+}
+
+/* Return a printable representation of FN without special characters.  The
+   caller is responsible for freeing memory if this returns something other
+   than its argument.  If FLAGS is non-zero, we are printing for portable
+   re-input and should single-quote filenames appropriately. */
+char *
+printable_filename (fn, flags)
+     char *fn;
+     int flags;
+{
+  char *newf;
+
+  if (ansic_shouldquote (fn))
+    newf = ansic_quote (fn, 0, NULL);
+  else if (flags && sh_contains_shell_metas (fn))
+    newf = sh_single_quote (fn);
+  else
+    newf = fn;
+
+  return newf;
+}
+
 /* Given a string containing units of information separated by colons,
    return the next one pointed to by (P_INDEX), or NULL if there are no more.
    Advance (P_INDEX) to the character after the colon. */
@@ -817,16 +1007,11 @@ extract_colon_unit (string, p_index)
       if (string[i])
        (*p_index)++;
       /* Return "" in the case of a trailing `:'. */
-      value = xmalloc (1);
+      value = (char *)xmalloc (1);
       value[0] = '\0';
     }
   else
-    {
-      len = i - start;
-      value = xmalloc (1 + len);
-      strncpy (value, string + start, len);
-      value [len] = '\0';
-    }
+    value = substring (string, start, i);
 
   return (value);
 }
@@ -841,6 +1026,11 @@ extract_colon_unit (string, p_index)
 extern char *get_dirstack_from_string __P((char *));
 #endif
 
+static char **bash_tilde_prefixes;
+static char **bash_tilde_prefixes2;
+static char **bash_tilde_suffixes;
+static char **bash_tilde_suffixes2;
+
 /* If tilde_expand hasn't been able to expand the text, perhaps it
    is a special shell expansion.  This function is installed as the
    tilde_expansion_preexpansion_hook.  It knows how to expand ~- and ~+.
@@ -859,7 +1049,7 @@ bash_special_tilde_expansions (text)
   else if (text[0] == '-' && text[1] == '\0')
     result = get_string_value ("OLDPWD");
 #if defined (PUSHD_AND_POPD)
-  else if (isdigit (*text) || ((*text == '+' || *text == '-') && isdigit (text[1])))
+  else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1])))
     result = get_dirstack_from_string (text);
 #endif
 
@@ -875,36 +1065,143 @@ tilde_initialize ()
   static int times_called = 0;
 
   /* Tell the tilde expander that we want a crack first. */
-  tilde_expansion_preexpansion_hook = (CPFunction *)bash_special_tilde_expansions;
+  tilde_expansion_preexpansion_hook = bash_special_tilde_expansions;
 
   /* Tell the tilde expander about special strings which start a tilde
      expansion, and the special strings that end one.  Only do this once.
      tilde_initialize () is called from within bashline_reinitialize (). */
   if (times_called++ == 0)
     {
-      tilde_additional_prefixes = (char **)xmalloc (3 * sizeof (char *));
-      tilde_additional_prefixes[0] = "=~";
-      tilde_additional_prefixes[1] = ":~";
-      tilde_additional_prefixes[2] = (char *)NULL;
-
-      tilde_additional_suffixes = (char **)xmalloc (3 * sizeof (char *));
-      tilde_additional_suffixes[0] = ":";
-      tilde_additional_suffixes[1] = "=~";
-      tilde_additional_suffixes[2] = (char *)NULL;
+      bash_tilde_prefixes = strvec_create (3);
+      bash_tilde_prefixes[0] = "=~";
+      bash_tilde_prefixes[1] = ":~";
+      bash_tilde_prefixes[2] = (char *)NULL;
+
+      bash_tilde_prefixes2 = strvec_create (2);
+      bash_tilde_prefixes2[0] = ":~";
+      bash_tilde_prefixes2[1] = (char *)NULL;
+
+      tilde_additional_prefixes = bash_tilde_prefixes;
+
+      bash_tilde_suffixes = strvec_create (3);
+      bash_tilde_suffixes[0] = ":";
+      bash_tilde_suffixes[1] = "=~";   /* XXX - ?? */
+      bash_tilde_suffixes[2] = (char *)NULL;
+
+      tilde_additional_suffixes = bash_tilde_suffixes;
+
+      bash_tilde_suffixes2 = strvec_create (2);
+      bash_tilde_suffixes2[0] = ":";
+      bash_tilde_suffixes2[1] = (char *)NULL;
+    }
+}
+
+/* POSIX.2, 3.6.1:  A tilde-prefix consists of an unquoted tilde character
+   at the beginning of the word, followed by all of the characters preceding
+   the first unquoted slash in the word, or all the characters in the word
+   if there is no slash...If none of the characters in the tilde-prefix are
+   quoted, the characters in the tilde-prefix following the tilde shell be
+   treated as a possible login name. */
+
+#define TILDE_END(c)   ((c) == '\0' || (c) == '/' || (c) == ':')
+
+static int
+unquoted_tilde_word (s)
+     const char *s;
+{
+  const char *r;
+
+  for (r = s; TILDE_END(*r) == 0; r++)
+    {
+      switch (*r)
+       {
+       case '\\':
+       case '\'':
+       case '"':
+         return 0;
+       }
     }
+  return 1;
 }
 
+/* Find the end of the tilde-prefix starting at S, and return the tilde
+   prefix in newly-allocated memory.  Return the length of the string in
+   *LENP.  FLAGS tells whether or not we're in an assignment context --
+   if so, `:' delimits the end of the tilde prefix as well. */
 char *
-bash_tilde_expand (s)
-     char *s;
+bash_tilde_find_word (s, flags, lenp)
+     const char *s;
+     int flags, *lenp;
 {
-  int old_immed;
+  const char *r;
   char *ret;
+  int l;
 
+  for (r = s; *r && *r != '/'; r++)
+    {
+      /* Short-circuit immediately if we see a quote character.  Even though
+        POSIX says that `the first unquoted slash' (or `:') terminates the
+        tilde-prefix, in practice, any quoted portion of the tilde prefix
+        will cause it to not be expanded. */
+      if (*r == '\\' || *r == '\'' || *r == '"')  
+       {
+         ret = savestring (s);
+         if (lenp)
+           *lenp = 0;
+         return ret;
+       }
+      else if (flags && *r == ':')
+       break;
+    }
+  l = r - s;
+  ret = xmalloc (l + 1);
+  strncpy (ret, s, l);
+  ret[l] = '\0';
+  if (lenp)
+    *lenp = l;
+  return ret;
+}
+    
+/* Tilde-expand S by running it through the tilde expansion library.
+   ASSIGN_P is 1 if this is a variable assignment, so the alternate
+   tilde prefixes should be enabled (`=~' and `:~', see above).  If
+   ASSIGN_P is 2, we are expanding the rhs of an assignment statement,
+   so `=~' is not valid. */
+char *
+bash_tilde_expand (s, assign_p)
+     const char *s;
+     int assign_p;
+{
+  int old_immed, old_term, r;
+  char *ret;
+
+#if 0
   old_immed = interrupt_immediately;
-  interrupt_immediately = 1;
-  ret = tilde_expand (s);
+  old_term = terminate_immediately;
+  /* We want to be able to interrupt tilde expansion. Ordinarily, we can just
+     jump to top_level, but we don't want to run any trap commands in a signal
+     handler context.  We might be able to get away with just checking for
+     things like SIGINT and SIGQUIT. */
+  if (any_signals_trapped () < 0)
+    interrupt_immediately = 1;
+  terminate_immediately = 1;
+#endif
+
+  tilde_additional_prefixes = assign_p == 0 ? (char **)0
+                                           : (assign_p == 2 ? bash_tilde_prefixes2 : bash_tilde_prefixes);
+  if (assign_p == 2)
+    tilde_additional_suffixes = bash_tilde_suffixes2;
+
+  r = (*s == '~') ? unquoted_tilde_word (s) : 1;
+  ret = r ? tilde_expand (s) : savestring (s);
+
+#if 0
   interrupt_immediately = old_immed;
+  terminate_immediately = old_term;
+#endif
+
+  QUIT;
+
   return (ret);
 }
 
@@ -923,20 +1220,6 @@ static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL;
 #  define NOGROUP (gid_t) -1
 #endif
 
-#if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX)
-#  define getmaxgroups() sysconf(_SC_NGROUPS_MAX)
-#else
-#  if defined (NGROUPS_MAX)
-#    define getmaxgroups() NGROUPS_MAX
-#  else /* !NGROUPS_MAX */
-#    if defined (NGROUPS)
-#      define getmaxgroups() NGROUPS
-#    else /* !NGROUPS */
-#      define getmaxgroups() 64
-#    endif /* !NGROUPS */
-#  endif /* !NGROUPS_MAX */
-#endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */
-
 static void
 initialize_group_array ()
 {
@@ -968,7 +1251,7 @@ initialize_group_array ()
   if (i == ngroups && ngroups < maxgroups)
     {
       for (i = ngroups; i > 0; i--)
-        group_array[i] = group_array[i - 1];
+       group_array[i] = group_array[i - 1];
       group_array[0] = current_user.gid;
       ngroups++;
     }
@@ -979,8 +1262,8 @@ initialize_group_array ()
   if (group_array[0] != current_user.gid)
     {
       for (i = 0; i < ngroups; i++)
-        if (group_array[i] == current_user.gid)
-          break;
+       if (group_array[i] == current_user.gid)
+         break;
       if (i < ngroups)
        {
          group_array[i] = group_array[0];
@@ -1029,7 +1312,6 @@ get_group_list (ngp)
 {
   static char **group_vector = (char **)NULL;
   register int i;
-  char *nbuf;
 
   if (group_vector)
     {
@@ -1048,13 +1330,102 @@ get_group_list (ngp)
       return (char **)NULL;
     }
 
-  group_vector = (char **)xmalloc (ngroups * sizeof (char *));
+  group_vector = strvec_create (ngroups);
   for (i = 0; i < ngroups; i++)
+    group_vector[i] = itos (group_array[i]);
+
+  if (ngp)
+    *ngp = ngroups;
+  return group_vector;
+}
+
+int *
+get_group_array (ngp)
+     int *ngp;
+{
+  int i;
+  static int *group_iarray = (int *)NULL;
+
+  if (group_iarray)
     {
-      nbuf = itos ((int)group_array[i]);
-      group_vector[i] = nbuf;
+      if (ngp)
+       *ngp = ngroups;
+      return (group_iarray);
     }
+
+  if (ngroups == 0)
+    initialize_group_array ();    
+
+  if (ngroups <= 0)
+    {
+      if (ngp)
+       *ngp = 0;
+      return (int *)NULL;
+    }
+
+  group_iarray = (int *)xmalloc (ngroups * sizeof (int));
+  for (i = 0; i < ngroups; i++)
+    group_iarray[i] = (int)group_array[i];
+
   if (ngp)
     *ngp = ngroups;
-  return group_vector;
+  return group_iarray;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*       Miscellaneous functions                                   */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return a value for PATH that is guaranteed to find all of the standard
+   utilities.  This uses Posix.2 configuration variables, if present.  It
+   uses a value defined in config.h as a last resort. */
+char *
+conf_standard_path ()
+{
+#if defined (_CS_PATH) && defined (HAVE_CONFSTR)
+  char *p;
+  size_t len;
+
+  len = (size_t)confstr (_CS_PATH, (char *)NULL, (size_t)0);
+  if (len > 0)
+    {
+      p = (char *)xmalloc (len + 2);
+      *p = '\0';
+      confstr (_CS_PATH, p, len);
+      return (p);
+    }
+  else
+    return (savestring (STANDARD_UTILS_PATH));
+#else /* !_CS_PATH || !HAVE_CONFSTR  */
+#  if defined (CS_PATH)
+  return (savestring (CS_PATH));
+#  else
+  return (savestring (STANDARD_UTILS_PATH));
+#  endif /* !CS_PATH */
+#endif /* !_CS_PATH || !HAVE_CONFSTR */
+}
+
+int
+default_columns ()
+{
+  char *v;
+  int c;
+
+  c = -1;
+  v = get_string_value ("COLUMNS");
+  if (v && *v)
+    {
+      c = atoi (v);
+      if (c > 0)
+       return c;
+    }
+
+  if (check_window_size)
+    get_new_window_size (0, (int *)0, &c);
+
+  return (c > 0 ? c : 80);
 }
+
+