Imported from ../bash-2.05b.tar.gz.
[platform/upstream/bash.git] / general.c
index 9ccfce6..446e42f 100644 (file)
--- a/general.c
+++ b/general.c
@@ -1,7 +1,6 @@
 /* general.c -- Stuff that is used by all files. */
 
-/* Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992
-   Free Software Foundation, Inc.
+/* Copyright (C) 1987-2002 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 
    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. */
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include "config.h"
 
-#include "config.h"    /* includes unistd.h for us */
-#include <stdio.h>
-#include <ctype.h>
-#include <errno.h>
 #include "bashtypes.h"
-#include <sys/param.h>
-#if defined (_POSIX_VERSION)
-#  if defined (amiga) && defined (USGr4)
-#    define _POSIX_SOURCE
-#  endif
-#  include <signal.h>
-#  if defined (amiga) && defined (USGr4)
-#    undef _POSIX_SOURCE
-#  endif
-#endif /* _POSIX_VERSION */
+#ifndef _MINIX
+#  include <sys/param.h>
+#endif
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
 #include "filecntl.h"
 #include "bashansi.h"
+#include <stdio.h>
+#include "chartypes.h"
+#include <errno.h>
+
 #include "shell.h"
 #include <tilde/tilde.h>
 
-#if !defined (USG) || defined (HAVE_RESOURCE)
-#  include <sys/time.h>
-#endif
-
-#include <sys/times.h>
-#include "maxpath.h"
-
 #if !defined (errno)
 extern int errno;
 #endif /* !errno */
 
-/* Make the functions strchr and strrchr if they do not exist. */
-#if !defined (HAVE_STRCHR)
-char *
-strchr (string, c)
-     char *string;
-     int c;
-{
-  register int i;
+extern int expand_aliases;
+extern int interrupt_immediately;
+extern int interactive_comments;
+extern int check_hashed_filenames;
+extern int source_uses_path;
+extern int source_searches_cwd;
 
-  for (i = 0; string && string[i]; i++)
-    if (string[i] == c)
-      return ((char *) (string + i));
+static char *bash_special_tilde_expansions __P((char *));
+static int unquoted_tilde_word __P((const char *));
+static void initialize_group_array __P((void));
 
-  return ((char *) NULL);
-}
+/* A standard error message to use when getcwd() returns NULL. */
+char *bash_getcwd_errstr = "getcwd: cannot access parent directories";
 
-char *
-strrchr (string, c)
-     char *string;
-     int c;
+/* Do whatever is necessary to initialize `Posix mode'. */
+void
+posix_initialize (on)
+     int on;
 {
-  register int i;
-
-  if (string)
-    i = strlen (string) - 1;
-  else
-    i = -1;
-
-  for (; string && i > -1; i--)
-    if (string[i] == c)
-      return ((char *) (string + i));
+  /* Things that should be turned on when posix mode is enabled. */
+  if (on != 0)
+    {
+      interactive_comments = source_uses_path = expand_aliases = 1;
+    }
 
-  return ((char *) NULL);
+  /* Things that should be turned on when posix mode is disabled. */
+  if (on == 0)
+    {
+      source_searches_cwd = 1;
+      expand_aliases = interactive_shell;
+    }
 }
-#endif /* !HAVE_STRCHR */
 
 /* **************************************************************** */
 /*                                                                 */
-/*                Memory Allocation and Deallocation.              */
+/*  Functions to convert to and from and display non-standard types */
 /*                                                                 */
 /* **************************************************************** */
 
-char *
-xmalloc (size)
-     int size;
+#if defined (RLIMTYPE)
+RLIMTYPE
+string_to_rlimtype (s)
+     char *s;
 {
-  register char *temp = (char *)malloc (size);
+  RLIMTYPE ret;
+  int neg;
 
-  if (!temp)
-    fatal_error ("Out of virtual memory!");
-
-  return (temp);
+  ret = 0;
+  neg = 0;
+  while (s && *s && whitespace (*s))
+    s++;
+  if (*s == '-' || *s == '+')
+    {
+      neg = *s == '-';
+      s++;
+    }
+  for ( ; s && *s && DIGIT (*s); s++)
+    ret = (ret * 10) + TODIGIT (*s);
+  return (neg ? -ret : ret);
 }
 
-char *
-xrealloc (pointer, size)
-     GENPTR pointer;
-     int size;
+void
+print_rlimtype (n, addnl)
+     RLIMTYPE n;
+     int addnl;
 {
-  char *temp;
+  char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p;
 
-  if (!pointer)
-    temp = xmalloc (size);
-  else
-    temp = (char *)realloc (pointer, size);
+  p = s + sizeof(s);
+  *--p = '\0';
 
-  if (!temp)
-    fatal_error ("Out of virtual memory!");
+  if (n < 0)
+    {
+      do
+       *--p = '0' - n % 10;
+      while ((n /= 10) != 0);
 
-  return (temp);
-}
+      *--p = '-';
+    }
+  else
+    {
+      do
+       *--p = '0' + n % 10;
+      while ((n /= 10) != 0);
+    }
 
-/* Use this as the function to call when adding unwind protects so we
-   don't need to know what free() returns. */
-void
-xfree (string)
-     char *string;
-{
-  free (string);
+  printf ("%s%s", p, addnl ? "\n" : "");
 }
+#endif /* RLIMTYPE */
 
 /* **************************************************************** */
 /*                                                                 */
-/*                  Integer to String Conversion                   */
+/*                    Input Validation Functions                   */
 /*                                                                 */
 /* **************************************************************** */
 
-/* Number of characters that can appear in a string representation
-   of an integer.  32 is larger than the string rep of 2^^31 - 1. */
-#define MAX_INT_LEN 32
-
-/* Integer to string conversion.  This conses the string; the
-   caller should free it. */
-char *
-itos (i)
-     int i;
-{
-  char *buf, *p, *ret;
-  int negative = 0;
-  unsigned int ui;
-
-  buf = xmalloc (MAX_INT_LEN);
-
-  if (i < 0)
-    {
-      negative++;
-      i = -i;
-    }
-
-  ui = (unsigned int) i;
-
-  buf[MAX_INT_LEN - 1] = '\0';
-  p = &buf[MAX_INT_LEN - 2];
-
-  do
-    *p-- = (ui % 10) + '0';
-  while (ui /= 10);
-
-  if (negative)
-    *p-- = '-';
-
-  ret = savestring (p + 1);
-  free (buf);
-  return (ret);
-}
-
 /* Return non-zero if all of the characters in STRING are digits. */
 int
 all_digits (string)
      char *string;
 {
-  while (*string)
-    {
-      if (!digit (*string))
-       return (0);
-      else
-       string++;
-    }
-  return (1);
-}
+  register char *s;
 
-/* atol(3) is not universal */
-long
-string_to_long (s)
-     char *s;
-{
-  long ret = 0L;
-  int neg = 0;
+  for (s = string; *s; s++)
+    if (DIGIT (*s) == 0)
+      return (0);
 
-  while (s && *s && whitespace (*s))
-    s++;
-  if (*s == '-' || *s == '+')
-    {
-      neg = *s == '-';
-      s++;
-    }
-  for ( ; s && *s && digit (*s); s++)
-    ret = (ret * 10) + digit_value (*s);
-  return (neg ? -ret : ret);
+  return (1);
 }
 
-#if defined (RLIMTYPE)
-RLIMTYPE
-string_to_rlimtype (s)
-     char *s;
+/* Return non-zero if the characters pointed to by STRING constitute a
+   valid number.  Stuff the converted number into RESULT if RESULT is
+   not null. */
+int
+legal_number (string, result)
+     char *string;
+     intmax_t *result;
 {
-  RLIMTYPE ret = 0;
-  int neg = 0;
+  intmax_t value;
+  char *ep;
 
-  while (s && *s && whitespace (*s))
-    s++;
-  if (*s == '-' || *s == '+')
-    {
-      neg = *s == '-';
-      s++;
-    }
-  for ( ; s && *s && digit (*s); s++)
-    ret = (ret * 10) + digit_value (*s);
-  return (neg ? -ret : ret);
-}
+  if (result)
+    *result = 0;
 
-void
-print_rlimtype (n, addnl)
-     RLIMTYPE n;
-     int addnl;
-{
-  char s[sizeof (RLIMTYPE) * 3 + 1];
-  int len = sizeof (RLIMTYPE) * 3 + 1;
+  errno = 0;
+  value = strtoimax (string, &ep, 10);
+  if (errno)
+    return 0;  /* errno is set on overflow or underflow */
 
-  if (n == 0)
-    {
-      printf ("0%s", addnl ? "\n" : "");
-      return;
-    }
+  /* Skip any trailing whitespace, since strtoimax does not. */
+  while (whitespace (*ep))
+    ep++;
 
-  if (n < 0)
+  /* If *string is not '\0' but *ep is '\0' on return, the entire string
+     is valid. */
+  if (string && *string && *ep == '\0')
     {
-      putchar ('-');
-      n = -n;
+      if (result)
+       *result = value;
+      /* The SunOS4 implementation of strtol() will happily ignore
+        overflow conditions, so this cannot do overflow correctly
+        on those systems. */
+      return 1;
     }
-
-  s[--len] = '\0';
-  for ( ; n != 0; n /= 10)
-    s[--len] = n % 10 + '0';
-  printf ("%s%s", s + len, addnl ? "\n" : "");
+    
+  return (0);
 }
-#endif /* RLIMTYPE */
 
 /* Return 1 if this token is a legal shell `identifier'; that is, it consists
    solely of letters, digits, and underscores, and does not begin with a
@@ -266,14 +199,15 @@ legal_identifier (name)
      char *name;
 {
   register char *s;
+  unsigned char c;
 
-  if (!name || !*name || digit (*name))
+  if (!name || !(c = *name) || (legal_variable_starter (c) == 0))
     return (0);
 
-  for (s = name; s && *s; s++)
+  for (s = name + 1; (c = *s) != 0; s++)
     {
-      if (!isletter (*s) && !digit (*s) && (*s != '_'))
-        return (0);
+      if (legal_variable_char (c) == 0)
+       return (0);
     }
   return (1);
 }
@@ -283,392 +217,248 @@ legal_identifier (name)
    does it consist of all digits.  If CHECK_WORD is non-zero,
    the word is checked to ensure that it consists of only letters,
    digits, and underscores. */
+int
 check_identifier (word, check_word)
      WORD_DESC *word;
      int check_word;
 {
-  if (word->dollar_present || word->quoted || all_digits (word->word))
+  if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word))
     {
-      report_error ("`%s' is 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)
     {
-      report_error ("`%s' is not a valid identifier", word->word);
+      internal_error ("`%s': not a valid identifier", word->word);
       return (0);
     }
   else
     return (1);
 }
 
-/* A function to unset no-delay mode on a file descriptor.  Used in shell.c
-   to unset it on the fd passed as stdin.  Should be called on stdin if
-   readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */
-
-#if !defined (O_NDELAY)
-#  if defined (FNDELAY)
-#    define O_NDELAY FNDELAY
-#  endif
-#endif /* O_NDELAY */
-
-/* Make sure no-delay mode is not set on file descriptor FD. */
-void
-unset_nodelay_mode (fd)
-     int fd;
+/* Returns non-zero if STRING is an assignment statement.  The returned value
+   is the index of the `=' sign. */
+int
+assignment (string)
+     const char *string;
 {
-  int flags, set = 0;
+  register unsigned char c;
+  register int newi, indx;
 
-  if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
-    return;
+  c = string[indx = 0];
 
-#if defined (O_NONBLOCK)
-  if (flags & O_NONBLOCK)
-    {
-      flags &= ~O_NONBLOCK;
-      set++;
-    }
-#endif /* O_NONBLOCK */
+  if (legal_variable_starter (c) == 0)
+    return (0);
 
-#if defined (O_NDELAY)
-  if (flags & O_NDELAY)
+  while (c = string[indx])
     {
-      flags &= ~O_NDELAY;
-      set++;
-    }
-#endif /* O_NDELAY */
+      /* The following is safe.  Note that '=' at the start of a word
+        is not an assignment statement. */
+      if (c == '=')
+       return (indx);
 
-  if (set)
-    fcntl (fd, F_SETFL, flags);
-}
+#if defined (ARRAY_VARS)
+      if (c == '[')
+       {
+         newi = skipsubscript (string, indx);
+         if (string[newi++] != ']')
+           return (0);
+         return ((string[newi] == '=') ? newi : 0);
+       }
+#endif /* ARRAY_VARS */
 
+      /* Variable names in assignment statements may contain only letters,
+        digits, and `_'. */
+      if (legal_variable_char (c) == 0)
+       return (0);
+
+      indx++;
+    }
+  return (0);
+}
 
 /* **************************************************************** */
 /*                                                                 */
-/*                     Generic List Functions                      */
+/*          Functions to manage files and file descriptors         */
 /*                                                                 */
 /* **************************************************************** */
 
-/* Call FUNCTION on every member of LIST, a generic list. */
-void
-map_over_list (list, function)
-     GENERIC_LIST *list;
-     Function *function;
-{
-  while (list)
-    {
-      (*function) (list);
-      list = list->next;
-    }
-}
+/* A function to unset no-delay mode on a file descriptor.  Used in shell.c
+   to unset it on the fd passed as stdin.  Should be called on stdin if
+   readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */
 
-/* Call FUNCTION on every string in WORDS. */
-void
-map_over_words (words, function)
-     WORD_LIST *words;
-     Function *function;
-{
-  while (words)
-    {
-      (*function)(words->word->word);
-      words = words->next;
-    }
-}
+#if !defined (O_NDELAY)
+#  if defined (FNDELAY)
+#    define O_NDELAY FNDELAY
+#  endif
+#endif /* O_NDELAY */
 
-/* Reverse the chain of structures in LIST.  Output the new head
-   of the chain.  You should always assign the output value of this
-   function to something, or you will lose the chain. */
-GENERIC_LIST *
-reverse_list (list)
-     GENERIC_LIST *list;
+/* Make sure no-delay mode is not set on file descriptor FD. */
+int
+sh_unset_nodelay_mode (fd)
+     int fd;
 {
-  register GENERIC_LIST *next, *prev = (GENERIC_LIST *)NULL;
+  int flags, bflags;
 
-  while (list)
-    {
-      next = list->next;
-      list->next = prev;
-      prev = list;
-      list = next;
-    }
-  return (prev);
-}
+  if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
+    return -1;
 
-/* Return the number of elements in LIST, a generic list. */
-int
-list_length (list)
-     GENERIC_LIST *list;
-{
-  register int i;
+  bflags = 0;
 
-  for (i = 0; list; list = list->next, i++);
-  return (i);
-}
+  /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present
+     and O_NDELAY is defined. */
+#ifdef O_NONBLOCK
+  bflags |= O_NONBLOCK;
+#endif
 
-/* A global variable which acts as a sentinel for an `error' list return. */
-GENERIC_LIST global_error_list;
-
-/* Delete the element of LIST which satisfies the predicate function COMPARER.
-   Returns the element that was deleted, so you can dispose of it, or -1 if
-   the element wasn't found.  COMPARER is called with the list element and
-   then ARG.  Note that LIST contains the address of a variable which points
-   to the list.  You might call this function like this:
-
-   SHELL_VAR *elt = delete_element (&variable_list, check_var_has_name, "foo");
-   dispose_variable (elt);
-*/
-GENERIC_LIST *
-delete_element (list, comparer, arg)
-     GENERIC_LIST **list;
-     Function *comparer;
-     char *arg;
-{
-  register GENERIC_LIST *prev = (GENERIC_LIST *)NULL;
-  register GENERIC_LIST *temp = *list;
+#ifdef O_NDELAY
+  bflags |= O_NDELAY;
+#endif
 
-  while (temp)
+  if (flags & bflags)
     {
-      if ((*comparer) (temp, arg))
-       {
-         if (prev)
-           prev->next = temp->next;
-         else
-           *list = temp->next;
-         return (temp);
-       }
-      prev = temp;
-      temp = temp->next;
+      flags &= ~bflags;
+      return (fcntl (fd, F_SETFL, flags));
     }
-  return ((GENERIC_LIST *)&global_error_list);
-}
-
-/* Find NAME in ARRAY.  Return the index of NAME, or -1 if not present.
-   ARRAY should be NULL terminated. */
-int
-find_name_in_list (name, array)
-     char *name, **array;
-{
-  int i;
-
-  for (i = 0; array[i]; i++)
-    if (strcmp (name, array[i]) == 0)
-      return (i);
 
-  return (-1);
+  return 0;
 }
 
-/* Return the length of ARRAY, a NULL terminated array of char *. */
+/* Return 1 if file descriptor FD is valid; 0 otherwise. */
 int
-array_len (array)
-     char **array;
+sh_validfd (fd)
+     int fd;
 {
-  register int i;
-  for (i = 0; array[i]; i++);
-  return (i);
+  return (fcntl (fd, F_GETFD, 0) >= 0);
 }
 
-/* Free the contents of ARRAY, a NULL terminated array of char *. */
-void
-free_array (array)
-     char **array;
-{
-  register int i = 0;
+/* There is a bug in the NeXT 2.1 rlogind that causes opens
+   of /dev/tty to fail. */
 
-  if (!array) return;
+#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__ */
 
-  while (array[i])
-    free (array[i++]);
-  free (array);
-}
-
-/* Allocate and return a new copy of ARRAY and its contents. */
-char **
-copy_array (array)
-     char **array;
+void
+check_dev_tty ()
 {
-  register int i;
-  int len;
-  char **new_array;
+  int tty_fd;
+  char *tty;
 
-  len = array_len (array);
+  tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK);
 
-  new_array = (char **)xmalloc ((len + 1) * sizeof (char *));
-  for (i = 0; array[i]; i++)
-    new_array[i] = savestring (array[i]);
-  new_array[i] = (char *)NULL;
-
-  return (new_array);
+  if (tty_fd < 0)
+    {
+      tty = (char *)ttyname (fileno (stdin));
+      if (tty == 0)
+       return;
+      tty_fd = open (tty, O_RDWR|O_NONBLOCK);
+    }
+  close (tty_fd);
 }
 
-/* Comparison routine for use with qsort() on arrays of strings. */
+/* Return 1 if PATH1 and PATH2 are the same file.  This is kind of
+   expensive.  If non-NULL STP1 and STP2 point to stat structures
+   corresponding to PATH1 and PATH2, respectively. */
 int
-qsort_string_compare (s1, s2)
-     register char **s1, **s2;
+same_file (path1, path2, stp1, stp2)
+     char *path1, *path2;
+     struct stat *stp1, *stp2;
 {
-  int result;
-
-  if ((result = **s1 - **s2) == 0)
-    result = strcmp (*s1, *s2);
-
-  return (result);
-}
+  struct stat st1, st2;
 
-/* Append LIST2 to LIST1.  Return the header of the list. */
-GENERIC_LIST *
-list_append (head, tail)
-     GENERIC_LIST *head, *tail;
-{
-  register GENERIC_LIST *t_head = head;
+  if (stp1 == NULL)
+    {
+      if (stat (path1, &st1) != 0)
+       return (0);
+      stp1 = &st1;
+    }
 
-  if (!t_head)
-    return (tail);
+  if (stp2 == NULL)
+    {
+      if (stat (path2, &st2) != 0)
+       return (0);
+      stp2 = &st2;
+    }
 
-  while (t_head->next)
-    t_head = t_head->next;
-  t_head->next = tail;
-  return (head);
+  return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino));
 }
 
-/* Some random string stuff. */
-
-/* Remove all leading whitespace from STRING.  This includes
-   newlines.  STRING should be terminated with a zero. */
-void
-strip_leading (string)
-     char *string;
+/* Move FD to a number close to the maximum number of file descriptors
+   allowed in the shell process, to avoid the user stepping on it with
+   redirection and causing us extra work.  If CHECK_NEW is non-zero,
+   we check whether or not the file descriptors are in use before
+   duplicating FD onto them.  MAXFD says where to start checking the
+   file descriptors.  If it's less than 20, we get the maximum value
+   available from getdtablesize(2). */
+int
+move_to_high_fd (fd, check_new, maxfd)
+     int fd, check_new, maxfd;
 {
-  char *start = string;
-
-  while (*string && (whitespace (*string) || *string == '\n'))
-    string++;
+  int script_fd, nfds, ignore;
 
-  if (string != start)
+  if (maxfd < 20)
     {
-      int len = strlen (string);
-      FASTCOPY (string, start, len);
-      start[len] = '\0';
+      nfds = getdtablesize ();
+      if (nfds <= 0)
+       nfds = 20;
+      if (nfds > HIGH_FD_MAX)
+       nfds = HIGH_FD_MAX;             /* reasonable maximum */
     }
-}
+  else
+    nfds = maxfd;
 
-/* Remove all trailing whitespace from STRING.  This includes
-   newlines.  If NEWLINES_ONLY is non-zero, only trailing newlines
-   are removed.  STRING should be terminated with a zero. */
-void
-strip_trailing (string, newlines_only)
-     char *string;
-     int newlines_only;
-{
-  int len = strlen (string) - 1;
+  for (nfds--; check_new && nfds > 3; nfds--)
+    if (fcntl (nfds, F_GETFD, &ignore) == -1)
+      break;
 
-  while (len >= 0)
+  if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1)
     {
-      if ((newlines_only && string[len] == '\n') ||
-          (!newlines_only && whitespace (string[len])))
-        len--;
-      else
-        break;
+      if (check_new == 0 || fd != fileno (stderr))     /* don't close stderr */
+       close (fd);
+      return (script_fd);
     }
-  string[len + 1] = '\0';
+
+  /* OK, we didn't find one less than our artificial maximum; return the
+     original file descriptor. */
+  return (fd);
 }
+/* Return non-zero if the characters from SAMPLE are not all valid
+   characters to be found in the first line of a shell script.  We
+   check up to the first newline, or SAMPLE_LEN, whichever comes first.
+   All of the characters must be printable or whitespace. */
 
-/* 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
+check_binary_file (sample, sample_len)
+     char *sample;
+     int sample_len;
 {
-  register int i, start;
-  char stub_char;
-  char *result;
-
-  /* The result cannot be larger than the input PATH. */
-  result = savestring (path);
-
-  stub_char = (*path == '/') ? '/' : '.';
+  register int i;
+  unsigned char c;
 
-  /* Walk along RESULT looking for things to compact. */
-  i = 0;
-  while (1)
+  for (i = 0; i < sample_len; i++)
     {
-      if (!result[i])
-       break;
-
-      while (result[i] && result[i] != '/')
-       i++;
-
-      start = i++;
-
-      /* If we didn't find any slashes, then there is nothing left to do. */
-      if (!result[start])
-       break;
-
-      /* Handle multiple `/'s in a row. */
-      while (result[i] == '/')
-       i++;
-
-#if !defined (apollo)
-      if ((start + 1) != i)
-#else
-      if ((start + 1) != i && (start != 0 || i != 2))
-#endif /* apollo */
-       {
-         strcpy (result + start + 1, result + i);
-         i = start + 1;
-       }
-
-#if 0
-      /* Handle backslash-quoted `/'. */
-      if (start > 0 && result[start - 1] == '\\')
-       continue;
-#endif
-
-      /* Check for trailing `/'. */
-      if (start && !result[i])
-       {
-       zero_last:
-         result[--i] = '\0';
-         break;
-       }
+      c = sample[i];
+      if (c == '\n')
+       return (0);
 
-      /* 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]))
-           {
-             while (--start > -1 && result[start] != '/');
-             strcpy (result + start + 1, result + i + 2);
-             i = (start < 0) ? 0 : start;
-             continue;
-           }
-       }
+      if (ISSPACE (c) == 0 && ISPRINT (c) == 0)
+       return (1);
     }
 
-  if (!*result)
-    {
-      *result = stub_char;
-      result[1] = '\0';
-    }
-  return (result);
+  return (0);
 }
 
+/* **************************************************************** */
+/*                                                                 */
+/*                 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
@@ -678,55 +468,33 @@ make_absolute (string, dot_path)
      char *string, *dot_path;
 {
   char *result;
-  int result_len;
-  
-  if (!dot_path || *string == '/')
+
+  if (dot_path == 0 || ABSPATH(string))
     result = savestring (string);
   else
-    {
-      if (dot_path && dot_path[0])
-       {
-         result = xmalloc (2 + strlen (dot_path) + strlen (string));
-         strcpy (result, dot_path);
-         result_len = strlen (result);
-         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;
-       }
-
-      strcpy (result + result_len, string);
-    }
+    result = sh_makepath (dot_path, string, 0);
 
   return (result);
 }
 
-/* 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++ == '.')
-    {
-      if (!*string || *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 == '.' && (string[1] == '\0' || string[1] == '/'))
-       return (1);
-    }
   return (0);
 }
 
@@ -735,9 +503,9 @@ absolute_pathname (string)
    up through $PATH. */
 int
 absolute_program (string)
-     char *string;
+     const char *string;
 {
-  return ((char *)strchr (string, '/') != (char *)NULL);
+  return ((char *)xstrchr (string, '/') != (char *)NULL);
 }
 
 /* Return the `basename' of the pathname in STRING (the stuff after the
@@ -748,14 +516,11 @@ base_pathname (string)
 {
   char *p;
 
-  if (!absolute_pathname (string))
+  if (absolute_pathname (string) == 0)
     return (string);
 
   p = (char *)strrchr (string, '/');
-  if (p)
-    return (++p);
-  else
-    return (string);
+  return (p ? ++p : string);
 }
 
 /* Return the full pathname of FILE.  Easy.  Filenames that begin
@@ -766,508 +531,373 @@ char *
 full_pathname (file)
      char *file;
 {
-  char *disposer;
+  char *ret;
 
-  if (*file == '~')
-    file = tilde_expand (file);
-  else
-    file = savestring (file);
+  file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file);
 
-  if ((*file == '/') && absolute_pathname (file))
+  if (ABSPATH(file))
     return (file);
 
-  disposer = file;
-
-  {
-    char *current_dir = xmalloc (2 + MAXPATHLEN + strlen (file));
-    int dlen;
-    if (getwd (current_dir) == 0)
-      {
-       report_error (current_dir);
-       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;
-
-    strcpy (current_dir + dlen, file);
-    free (disposer);
-    return (current_dir);
-  }
-}
-
-#if !defined (HAVE_STRCASECMP)
+  ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT));
+  free (file);
 
-#if !defined (to_upper)
-#  define to_upper(c) (islower(c) ? toupper(c) : (c))
-#endif /* to_upper */
-
-/* Compare at most COUNT characters from string1 to string2.  Case
-   doesn't matter. */
-int
-strnicmp (string1, string2, count)
-     char *string1, *string2;
-     int count;
-{
-  register char ch1, ch2;
-
-  while (count)
-    {
-      ch1 = *string1++;
-      ch2 = *string2++;
-      if (to_upper(ch1) == to_upper(ch2))
-       count--;
-      else
-       break;
-    }
-  return (count);
+  return (ret);
 }
 
-/* strcmp (), but caseless. */
-int
-stricmp (string1, string2)
-     char *string1, *string2;
+/* A slightly related function.  Get the prettiest name of this
+   directory possible. */
+static char tdir[PATH_MAX];
+
+/* Return a pretty pathname.  If the first part of the pathname is
+   the same as $HOME, then replace that with `~'.  */
+char *
+polite_directory_format (name)
+     char *name;
 {
-  register char ch1, ch2;
+  char *home;
+  int l;
 
-  while (*string1 && *string2)
+  home = get_string_value ("HOME");
+  l = home ? strlen (home) : 0;
+  if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/'))
     {
-      ch1 = *string1++;
-      ch2 = *string2++;
-      if (to_upper(ch1) != to_upper(ch2))
-       return (1);
+      strncpy (tdir + 1, name + l, sizeof(tdir) - 2);
+      tdir[0] = '~';
+      tdir[sizeof(tdir) - 1] = '\0';
+      return (tdir);
     }
-  return (*string1 - *string2);
+  else
+    return (name);
 }
-#endif /* !HAVE_STRCASECMP */
 
-/* Determine if s2 occurs in s1.  If so, return a pointer to the
-   match in s1.  The compare is case insensitive. */
+/* 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. */
 char *
-strindex (s1, s2)
-     char *s1, *s2;
+extract_colon_unit (string, p_index)
+     char *string;
+     int *p_index;
 {
-  register int i, l = strlen (s2);
-  register int len = strlen (s1);
-
-  for (i = 0; (len - i) >= l; i++)
-    if (strnicmp (s1 + i, s2, l) == 0)
-      return (s1 + i);
-  return ((char *)NULL);
-}
+  int i, start, len;
+  char *value;
 
-/* Set the environment variables $LINES and $COLUMNS in response to
-   a window size change. */
-void
-set_lines_and_columns (lines, cols)
-     int lines, cols;
-{
-  char *val;
+  if (string == 0)
+    return (string);
 
-  val = itos (lines);
-  bind_variable ("LINES", val);
-  free (val);
+  len = strlen (string);
+  if (*p_index >= len)
+    return ((char *)NULL);
 
-  val = itos (cols);
-  bind_variable ("COLUMNS", val);
-  free (val);
-}
+  i = *p_index;
 
-/* A wrapper for bcopy that can be prototyped in general.h */
-void
-xbcopy (s, d, n)
-     char *s, *d;
-     int n;
-{
-  FASTCOPY (s, d, n);
-}
+  /* Each call to this routine leaves the index pointing at a colon if
+     there is more to the path.  If I is > 0, then increment past the
+     `:'.  If I is 0, then the path has a leading colon.  Trailing colons
+     are handled OK by the `else' part of the if statement; an empty
+     string is returned in that case. */
+  if (i && string[i] == ':')
+    i++;
 
-/* Return a string corresponding to the error number E.  From
-   the ANSI C spec. */
-#if defined (strerror)
-#  undef strerror
-#endif
+  for (start = i; string[i] && string[i] != ':'; i++)
+    ;
 
-#if !defined (HAVE_STRERROR)
-char *
-strerror (e)
-     int e;
-{
-  extern int sys_nerr;
-  extern char *sys_errlist[];
-  static char emsg[40];
+  *p_index = i;
 
-  if (e > 0 && e < sys_nerr)
-    return (sys_errlist[e]);
-  else
+  if (i == start)
     {
-      sprintf (emsg, "Unknown error %d", e);
-      return (&emsg[0]);
+      if (string[i])
+       (*p_index)++;
+      /* Return "" in the case of a trailing `:'. */
+      value = (char *)xmalloc (1);
+      value[0] = '\0';
     }
+  else
+    value = substring (string, start, i);
+
+  return (value);
 }
-#endif /* HAVE_STRERROR */
 
-#if (defined (USG) && !defined (HAVE_TIMEVAL)) || defined (Minix)
-#  define TIMEVAL_MISSING
+/* **************************************************************** */
+/*                                                                 */
+/*                 Tilde Initialization and Expansion              */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (PUSHD_AND_POPD)
+extern char *get_dirstack_from_string __P((char *));
 #endif
 
-#if !defined (TIMEVAL_MISSING) || defined (HAVE_RESOURCE)
-/* Print the contents of a struct timeval * in a standard way. */
-void
-print_timeval (tvp)
-     struct timeval *tvp;
-{
-  int minutes, seconds_fraction;
-  long seconds;
+static char **bash_tilde_prefixes;
+static char **bash_tilde_suffixes;
 
-  seconds = tvp->tv_sec;
+/* 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 ~+.
+   If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the
+   directory stack. */
+static char *
+bash_special_tilde_expansions (text)
+     char *text;
+{
+  char *result;
 
-  seconds_fraction = tvp->tv_usec % 1000000;
-  seconds_fraction = (seconds_fraction * 100) / 1000000;
+  result = (char *)NULL;
 
-  minutes = seconds / 60;
-  seconds %= 60;
+  if (text[0] == '+' && text[1] == '\0')
+    result = get_string_value ("PWD");
+  else if (text[0] == '-' && text[1] == '\0')
+    result = get_string_value ("OLDPWD");
+#if defined (PUSHD_AND_POPD)
+  else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1])))
+    result = get_dirstack_from_string (text);
+#endif
 
-  printf ("%0dm%0ld.%02ds",  minutes, seconds, seconds_fraction);
+  return (result ? savestring (result) : (char *)NULL);
 }
-#endif /* !TIMEVAL_MISSING || HAVE_RESOURCE */
-
-/* Print the time defined by a time_t (returned by the `times' and `time'
-   system calls) in a standard way.  This is scaled in terms of HZ, which
-   is what is returned by the `times' call. */
-
-#if !defined (BrainDeath)
-#  if !defined (HZ)
-#    if defined (USG)
-#      define HZ 100           /* From my Sys V.3.2 manual for times(2) */
-#    else
-#      define HZ 60            /* HZ is always 60 on BSD systems */
-#    endif /* USG */
-#  endif /* HZ */
 
+/* Initialize the tilde expander.  In Bash, we handle `~-' and `~+', as
+   well as handling special tilde prefixes; `:~" and `=~' are indications
+   that we should do tilde expansion. */
 void
-print_time_in_hz (t)
-  time_t t;
+tilde_initialize ()
 {
-  int minutes, seconds_fraction;
-  long seconds;
+  static int times_called = 0;
 
-  seconds_fraction = t % HZ;
-  seconds_fraction = (seconds_fraction * 100) / HZ;
+  /* Tell the tilde expander that we want a crack first. */
+  tilde_expansion_preexpansion_hook = bash_special_tilde_expansions;
 
-  seconds = t / HZ;
+  /* 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)
+    {
+      bash_tilde_prefixes = strvec_create (3);
+      bash_tilde_prefixes[0] = "=~";
+      bash_tilde_prefixes[1] = ":~";
+      bash_tilde_prefixes[2] = (char *)NULL;
 
-  minutes = seconds / 60;
-  seconds %= 60;
+      tilde_additional_prefixes = bash_tilde_prefixes;
 
-  printf ("%0dm%0ld.%02ds",  minutes, seconds, seconds_fraction);
-}
-#endif /* BrainDeath */
+      bash_tilde_suffixes = strvec_create (3);
+      bash_tilde_suffixes[0] = ":";
+      bash_tilde_suffixes[1] = "=~";   /* XXX - ?? */
+      bash_tilde_suffixes[2] = (char *)NULL;
 
-#if !defined (HAVE_DUP2)
-/* Replacement for dup2 (), for those systems which either don't have it,
-   or supply one with broken behaviour. */
-int
-dup2 (fd1, fd2)
-     int fd1, fd2;
-{
-  extern int getdtablesize ();
-  int saved_errno, r;
-
-  /* If FD1 is not a valid file descriptor, then return immediately with
-     an error. */
-  if (fcntl (fd1, F_GETFL, 0) == -1)
-    return (-1);
-
-  if (fd2 < 0 || fd2 >= getdtablesize ())
-    {
-      errno = EBADF;
-      return (-1);
+      tilde_additional_suffixes = bash_tilde_suffixes;
     }
+}
 
-  if (fd1 == fd2)
-    return (0);
-
-  saved_errno = errno;
+/* 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. */
 
-  (void) close (fd2);
-  r = fcntl (fd1, F_DUPFD, fd2);
+#define TILDE_END(c)   ((c) == '\0' || (c) == '/' || (c) == ':')
 
-  if (r >= 0)
-    errno = saved_errno;
-  else
-    if (errno == EINVAL)
-      errno = EBADF;
-
-  /* Force the new file descriptor to remain open across exec () calls. */
-  SET_OPEN_ON_EXEC (fd2);
-  return (r);
-}
-#endif /* !HAVE_DUP2 */
-
-/*
- * Return the total number of available file descriptors.
- *
- * On some systems, like 4.2BSD and its descendents, there is a system call
- * that returns the size of the descriptor table: getdtablesize().  There are
- * lots of ways to emulate this on non-BSD systems.
- *
- * On System V.3, this can be obtained via a call to ulimit:
- *     return (ulimit(4, 0L));
- *
- * On other System V systems, NOFILE is defined in /usr/include/sys/param.h
- * (this is what we assume below), so we can simply use it:
- *     return (NOFILE);
- *
- * On POSIX systems, there are specific functions for retrieving various
- * configuration parameters:
- *     return (sysconf(_SC_OPEN_MAX));
- *
- */
-
-#if !defined (USG) && !defined (HPUX) && !defined (HAVE_GETDTABLESIZE)
-#  define HAVE_GETDTABLESIZE
-#endif /* !USG && !HPUX && !HAVE_GETDTABLESIZE */
-
-#if defined (hppa) && (defined (hpux_8) || defined (hpux_9))
-#  undef HAVE_GETDTABLESIZE
-#endif /* hppa && hpux_8 */
-
-#if !defined (HAVE_GETDTABLESIZE)
-int
-getdtablesize ()
+static int
+unquoted_tilde_word (s)
+     const char *s;
 {
-#  if defined (_POSIX_VERSION) && defined (_SC_OPEN_MAX)
-  return (sysconf(_SC_OPEN_MAX));      /* Posix systems use sysconf */
-#  else /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */
-#    if defined (USGr3)
-  return (ulimit (4, 0L));     /* System V.3 systems use ulimit(4, 0L) */
-#    else /* !USGr3 */
-#      if defined (NOFILE)     /* Other systems use NOFILE */
-  return (NOFILE);
-#      else /* !NOFILE */
-  return (20);                 /* XXX - traditional value is 20 */
-#      endif /* !NOFILE */
-#    endif /* !USGr3 */
-#  endif /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */
+  const char *r;
+
+  for (r = s; TILDE_END(*r) == 0; r++)
+    {
+      switch (*r)
+       {
+       case '\\':
+       case '\'':
+       case '"':
+         return 0;
+       }
+    }
+  return 1;
 }
-#endif /* !HAVE_GETDTABLESIZE */
 
-#if defined (USG)
+/* 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). */
+char *
+bash_tilde_expand (s, assign_p)
+     const char *s;
+     int assign_p;
+{
+  int old_immed, r;
+  char *ret;
+
+  old_immed = interrupt_immediately;
+  interrupt_immediately = 1;
+  tilde_additional_prefixes = assign_p ? bash_tilde_prefixes : (char **)0;
+  r = (*s == '~') ? unquoted_tilde_word (s) : 1;
+  ret = r ? tilde_expand (s) : savestring (s);
+  interrupt_immediately = old_immed;
+  return (ret);
+}
 
-#if !defined (HAVE_BCOPY)
-bcopy (s,d,n) char *d,*s; { FASTCOPY (s, d, n); }
-bzero (s,n) char *s; int n; { memset(s, '\0', n); }
-#endif /* !HAVE_BCOPY */
+/* **************************************************************** */
+/*                                                                 */
+/*       Functions to manipulate and search the group list         */
+/*                                                                 */
+/* **************************************************************** */
 
-#if !defined (HAVE_GETHOSTNAME)
-#include <sys/utsname.h>
-int
-gethostname (name, namelen)
-     char *name;
-     int namelen;
-{
-  int i;
-  struct utsname ut;
+static int ngroups, maxgroups;
 
-  --namelen;
+/* The set of groups that this user is a member of. */
+static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL;
 
-  uname (&ut);
-  i = strlen (ut.nodename) + 1;
-  strncpy (name, ut.nodename, i < namelen ? i : namelen);
-  name[namelen] = '\0';
-  return (0);
-}
-#endif /* !HAVE_GETHOSTNAME */
-#endif /* USG */
+#if !defined (NOGROUP)
+#  define NOGROUP (gid_t) -1
+#endif
 
-#if !defined (HAVE_GETWD)
-char *
-getwd (string)
-     char *string;
+static void
+initialize_group_array ()
 {
-  extern char *getcwd ();
-  char *result;
+  register int i;
 
-  result = getcwd (string, MAXPATHLEN);
-  if (result == NULL)
-    strcpy (string, "getwd: cannot access parent directories");
-  return (result);
-}
-#endif /* !HAVE_GETWD */
+  if (maxgroups == 0)
+    maxgroups = getmaxgroups ();
 
-/* A slightly related function.  Get the prettiest name of this
-   directory possible. */
-static char tdir[MAXPATHLEN];
+  ngroups = 0;
+  group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T));
 
-/* Return a pretty pathname.  If the first part of the pathname is
-   the same as $HOME, then replace that with `~'.  */
-char *
-polite_directory_format (name)
-     char *name;
-{
-  char *home = get_string_value ("HOME");
-  int l = home ? strlen (home) : 0;
+#if defined (HAVE_GETGROUPS)
+  ngroups = getgroups (maxgroups, group_array);
+#endif
 
-  if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/'))
+  /* If getgroups returns nothing, or the OS does not support getgroups(),
+     make sure the groups array includes at least the current gid. */
+  if (ngroups == 0)
     {
-      strcpy (tdir + 1, name + l);
-      tdir[0] = '~';
-      return (tdir);
+      group_array[0] = current_user.gid;
+      ngroups = 1;
     }
-  else
-    return (name);
-}
 
-#if defined (NO_READ_RESTART_ON_SIGNAL)
-static char localbuf[128];
-static int local_index = 0, local_bufused = 0;
+  /* If the primary group is not in the groups array, add it as group_array[0]
+     and shuffle everything else up 1, if there's room. */
+  for (i = 0; i < ngroups; i++)
+    if (current_user.gid == (gid_t)group_array[i])
+      break;
+  if (i == ngroups && ngroups < maxgroups)
+    {
+      for (i = ngroups; i > 0; i--)
+       group_array[i] = group_array[i - 1];
+      group_array[0] = current_user.gid;
+      ngroups++;
+    }
 
-/* Posix and USG systems do not guarantee to restart read () if it is
-   interrupted by a signal.  We do the read ourselves, and restart it
-   if it returns EINTR. */
-int
-getc_with_restart (stream)
-     FILE *stream;
-{
-  /* Try local buffering to reduce the number of read(2) calls. */
-  if (local_index == local_bufused || local_bufused == 0)
+  /* If the primary group is not group_array[0], swap group_array[0] and
+     whatever the current group is.  The vast majority of systems should
+     not need this; a notable exception is Linux. */
+  if (group_array[0] != current_user.gid)
     {
-      while (1)
+      for (i = 0; i < ngroups; i++)
+       if (group_array[i] == current_user.gid)
+         break;
+      if (i < ngroups)
        {
-         local_bufused = read (fileno (stream), localbuf, sizeof(localbuf));
-         if (local_bufused > 0)
-           break;
-         else if (local_bufused == 0 || errno != EINTR)
-           {
-             local_index = 0;
-             return EOF;
-           }
+         group_array[i] = group_array[0];
+         group_array[0] = current_user.gid;
        }
-      local_index = 0;
     }
-  return (localbuf[local_index++]);
 }
 
+/* Return non-zero if GID is one that we have in our groups list. */
 int
-ungetc_with_restart (c, fp)
-     int c;
-     FILE *fp;
+#if defined (__STDC__) || defined ( _MINIX)
+group_member (gid_t gid)
+#else
+group_member (gid)
+     gid_t gid;
+#endif /* !__STDC__ && !_MINIX */
 {
-  if (local_index == 0 || local_bufused == 0 || c == EOF)
-    return EOF;
-  return (localbuf[--local_index] = c);
-}
+#if defined (HAVE_GETGROUPS)
+  register int i;
+#endif
 
-#endif /* NO_READ_RESTART_ON_SIGNAL */
+  /* Short-circuit if possible, maybe saving a call to getgroups(). */
+  if (gid == current_user.gid || gid == current_user.egid)
+    return (1);
 
-#if defined (USG) || defined (AIX) || (defined (_POSIX_VERSION) && defined (Ultrix))
-/* USG and strict POSIX systems do not have killpg ().  But we use it in
-   jobs.c, nojobs.c and some of the builtins.  This can also be redefined
-   as a macro if necessary. */
-#if !defined (_POSIX_VERSION)
-#  define pid_t int
-#endif /* _POSIX_VERSION */
+#if defined (HAVE_GETGROUPS)
+  if (ngroups == 0)
+    initialize_group_array ();
 
-int
-killpg (pgrp, sig)
-     pid_t pgrp;
-     int sig;
-{
-  return (kill (-pgrp, sig));
-}
-#endif /* USG  || AIX || (_POSIX_VERSION && Ultrix) */
+  /* In case of error, the user loses. */
+  if (ngroups <= 0)
+    return (0);
 
-/* **************************************************************** */
-/*                                                                 */
-/*                 Tilde Initialization and Expansion              */
-/*                                                                 */
-/* **************************************************************** */
+  /* Search through the list looking for GID. */
+  for (i = 0; i < ngroups; i++)
+    if (gid == (gid_t)group_array[i])
+      return (1);
+#endif
 
-/* 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_failure_hook.  It knows how to expand ~- and ~+. */
-static char *
-bash_tilde_expand (text)
-     char *text;
+  return (0);
+}
+
+char **
+get_group_list (ngp)
+     int *ngp;
 {
-  char *result = (char *)NULL;
+  static char **group_vector = (char **)NULL;
+  register int i;
 
-  if (!text[1])
+  if (group_vector)
     {
-      if (*text == '+')
-        result = get_string_value ("PWD");
-      else if (*text == '-')
-        result = get_string_value ("OLDPWD");
+      if (ngp)
+       *ngp = ngroups;
+      return group_vector;
     }
 
-  if (result)
-    result = savestring (result);
+  if (ngroups == 0)
+    initialize_group_array ();
 
-  return (result);
+  if (ngroups <= 0)
+    {
+      if (ngp)
+       *ngp = 0;
+      return (char **)NULL;
+    }
+
+  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;
 }
 
-/* Initialize the tilde expander.  In Bash, we handle `~-' and `~+', as
-   well as handling special tilde prefixes; `:~" and `=~' are indications
-   that we should do tilde expansion. */
-void
-tilde_initialize ()
+int *
+get_group_array (ngp)
+     int *ngp;
 {
-  static int times_called = 0;
-
-  /* Tell the tilde expander that we want a crack if it fails. */
-  tilde_expansion_failure_hook = (CPFunction *)bash_tilde_expand;
+  int i;
+  static int *group_iarray = (int *)NULL;
 
-  /* 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)
+  if (group_iarray)
     {
-      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;
+      if (ngp)
+       *ngp = ngroups;
+      return (group_iarray);
     }
-  times_called++;
-}
-
-#if defined (_POSIX_VERSION)
 
-#if !defined (SA_INTERRUPT)
-#  define SA_INTERRUPT 0
-#endif
+  if (ngroups == 0)
+    initialize_group_array ();    
 
-#if !defined (SA_RESTART)
-#  define SA_RESTART 0
-#endif
+  if (ngroups <= 0)
+    {
+      if (ngp)
+       *ngp = 0;
+      return (int *)NULL;
+    }
 
-SigHandler *
-set_signal_handler (sig, handler)
-     int sig;
-     SigHandler *handler;
-{
-  struct sigaction act, oact;
+  group_iarray = (int *)xmalloc (ngroups * sizeof (int));
+  for (i = 0; i < ngroups; i++)
+    group_iarray[i] = (int)group_array[i];
 
-  act.sa_handler = handler;
-  act.sa_flags = 0;
-#if 0
-  if (sig == SIGALRM)
-    act.sa_flags |= SA_INTERRUPT;      /* XXX */
-  else
-    act.sa_flags |= SA_RESTART;                /* XXX */
-#endif
-  sigemptyset (&act.sa_mask);
-  sigemptyset (&oact.sa_mask);
-  sigaction (sig, &act, &oact);
-  return (oact.sa_handler);
+  if (ngp)
+    *ngp = ngroups;
+  return group_iarray;
 }
-#endif /* _POSIX_VERSION */