Imported from ../bash-4.0-rc1.tar.gz.
[platform/upstream/bash.git] / builtins / cd.def
index 338f694..d53b258 100644 (file)
 This file is cd.def, from which is created cd.c.  It implements the
-builtins "cd", "pwd", "pushd", "popd", and "dirs" in Bash.
+builtins "cd" and "pwd" in Bash.
 
-Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
+Copyright (C) 1987-2009 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 1, 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/>.
 
 $PRODUCES cd.c
+#include <config.h>
 
-#include <stdio.h>
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include "posixdir.h"
+#include "posixstat.h"
+#ifndef _MINIX
 #include <sys/param.h>
+#endif
 
-#if defined (HAVE_STRING_H)
-#  include <string.h>
-#else /* !HAVE_STRING_H */
-#  include <strings.h>
-#endif /* !HAVE_STRING_H */
+#include <stdio.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
 
 #include <errno.h>
 #include <tilde/tilde.h>
 
 #include "../shell.h"
 #include "../flags.h"
-#include "../maxpath.h"
+#include "maxpath.h"
 #include "common.h"
+#include "bashgetopt.h"
 
 #if !defined (errno)
 extern int errno;
 #endif /* !errno */
 
-static int change_to_directory (), cd_to_string ();
+extern int posixly_correct;
+extern int array_needs_making;
+extern const char * const bash_getcwd_errstr;
 
-$BUILTIN cd
-$FUNCTION cd_builtin
-$SHORT_DOC cd [dir]
-Change the current directory to DIR.  The variable $HOME is the
-default DIR.  The variable $CDPATH defines the search path for
-the directory containing DIR.  Alternative directory names are
-separated by a colon (:).  A null directory name is the same as
-the current directory, i.e. `.'.  If DIR begins with a slash (/),
-then $CDPATH is not used.  If the directory is not found, and the
-shell variable `cdable_vars' exists, then try the word as a variable
-name.  If that variable has a value, then cd to the value of that
-variable.
-$END
+static int bindpwd __P((int));
+static void setpwd __P((char *));
+static char *resetpwd __P((char *));
+static int change_to_directory __P((char *, int));
 
-/* This builtin is ultimately the way that all user-visible commands should
-   change the current working directory.  It is called by cd_to_string (),
-   so the programming interface is simple, and it handles errors and
-   restrictions properly. */
-int
-cd_builtin (list)
-     WORD_LIST *list;
-{
-  char *dirname;
+/* Change this to 1 to get cd spelling correction by default. */
+int cdspelling = 0;
 
-#if defined (RESTRICTED_SHELL)
-  if (restricted)
-    {
-      builtin_error ("restricted");
-      return (EXECUTION_FAILURE);
-    }
-#endif /* RESTRICTED_SHELL */
-
-  if (list)
-    {
-      char *extract_colon_unit ();
-      char *path_string = get_string_value ("CDPATH");
-      char *path;
-      int path_index = 0, dirlen, pathlen;
-
-      dirname = list->word->word;
+int cdable_vars;
 
-      if (path_string && !absolute_pathname (dirname))
-       {
-         while ((path = extract_colon_unit (path_string, &path_index)))
-           {
-             char *dir;
-
-             if (*path == '~')
-               {
-                 char *te_string = tilde_expand (path);
-
-                 free (path);
-                 path = te_string;
-               }
-
-             if (!*path)
-               {
-                 free (path);
-                 path = xmalloc (2);
-                 path[0] = '.';        /* by definition. */
-                 path[1] = '\0';
-               }
-
-             dirlen = strlen (dirname);
-             pathlen = strlen (path);
-             dir = xmalloc (2 + dirlen + pathlen);
-             strcpy (dir, path);
-             if (path[pathlen - 1] != '/')
-               {
-                 dir[pathlen++] = '/';
-                 dir[pathlen] = '\0';
-               }
-             strcpy (dir + pathlen, dirname);
-             free (path);
-
-             if (change_to_directory (dir))
-               {
-                 /* replaces (strncmp (dir, "./", 2) != 0) */
-                 if (dir[0] != '.' || dir[1] != '/')
-                   printf ("%s\n", dir);
-
-                 free (dir);
-                 goto bind_and_exit;
-               }
-             else
-               free (dir);
-           }
-       }
-
-      if (!change_to_directory (dirname))
-       {
-         /* Maybe this is `cd -', equivalent to `cd $OLDPWD' */
-         if (dirname[0] == '-' && dirname[1] == '\0')
-           {
-             char *t = get_string_value ("OLDPWD");
-
-             if (t && change_to_directory (t))
-               goto bind_and_exit;
-           }
-
-         /* If the user requests it, then perhaps this is the name of
-            a shell variable, whose value contains the directory to
-            change to.  If that is the case, then change to that
-            directory. */
-         if (find_variable ("cdable_vars"))
-           {
-             char *t = get_string_value (dirname);
+$BUILTIN cd
+$FUNCTION cd_builtin
+$SHORT_DOC cd [-L|-P] [dir]
+Change the shell working directory.
 
-             if (t && change_to_directory (t))
-               {
-                 printf ("%s\n", t);
-                 goto bind_and_exit;
-               }
-           }
+Change the current directory to DIR.  The default DIR is the value of the
+HOME shell variable.
 
-         file_error (dirname);
-         return (EXECUTION_FAILURE);
-       }
-      goto bind_and_exit;
-    }
-  else
-    {
-      dirname = get_string_value ("HOME");
+The variable CDPATH defines the search path for the directory containing
+DIR.  Alternative directory names in CDPATH are separated by a colon (:).
+A null directory name is the same as the current directory.  If DIR begins
+with a slash (/), then CDPATH is not used.
 
-      if (!dirname)
-       return (EXECUTION_FAILURE);
+If the directory is not found, and the shell option `cdable_vars' is set,
+the word is assumed to be  a variable name.  If that variable has a value,
+its value is used for DIR.
 
-      if (!change_to_directory (dirname))
-       {
-         file_error (dirname);
-         return (EXECUTION_FAILURE);
-       }
+Options:
+    -L force symbolic links to be followed
+    -P use the physical directory structure without following symbolic
+       links
 
-    bind_and_exit:
-      {
-       char *directory;
+The default is to follow symbolic links, as if `-L' were specified.
 
-       directory = get_working_directory ("cd");
+Exit Status:
+Returns 0 if the directory is changed; non-zero otherwise.
+$END
 
-       bind_variable ("OLDPWD", get_string_value ("PWD"));
-       bind_variable ("PWD", directory);
+/* Just set $PWD, don't change OLDPWD.  Used by `pwd -P' in posix mode. */
+static void
+setpwd (dirname)
+     char *dirname;
+{
+  int old_anm;
+  SHELL_VAR *tvar;
 
-       FREE (directory);
-      }
-      return (EXECUTION_SUCCESS);
+  old_anm = array_needs_making;
+  tvar = bind_variable ("PWD", dirname ? dirname : "", 0);
+  if (old_anm == 0 && array_needs_making && exported_p (tvar))
+    {
+      update_export_env_inplace ("PWD=", 4, dirname ? dirname : "");
+      array_needs_making = 0;
     }
 }
 
-$BUILTIN pwd
-$FUNCTION pwd_builtin
-$SHORT_DOC pwd
-Print the current working directory.
-$END
-
-/* Non-zero means that pwd always give verbatim directory, regardless of
-   symbolic link following. */
-static int verbatim_pwd;
-
-/* Print the name of the current working directory. */
-pwd_builtin (list)
-     WORD_LIST *list;
+static int
+bindpwd (no_symlinks)
+     int no_symlinks;
 {
-  char *directory, *s;
+  char *dirname, *pwdvar;
+  int old_anm, r;
+  SHELL_VAR *tvar;
 
-#if 0
-  no_args (list);
-#else
-  verbatim_pwd = no_symbolic_links;
-  if (list && (s = list->word->word) && s[0] == '-' && s[1] == 'P' && !s[2])
-    verbatim_pwd = 1;
-#endif
+  r = sh_chkwrite (EXECUTION_SUCCESS);
 
-  if (verbatim_pwd)
-    {
-      char *buffer = xmalloc (MAXPATHLEN);
-      directory = getwd (buffer);
+#define tcwd the_current_working_directory
+  dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd)
+                : get_working_directory ("cd");
+#undef tcwd
 
-      if (!directory)
-       {
-         builtin_error ("%s", buffer);
-         free (buffer);
-       }
-    }
-  else
-    directory = get_working_directory ("pwd");
+  old_anm = array_needs_making;
+  pwdvar = get_string_value ("PWD");
 
-  if (directory)
+  tvar = bind_variable ("OLDPWD", pwdvar, 0);
+  if (old_anm == 0 && array_needs_making && exported_p (tvar))
     {
-      printf ("%s\n", directory);
-      fflush (stdout);
-      free (directory);
-      return (EXECUTION_SUCCESS);
+      update_export_env_inplace ("OLDPWD=", 7, pwdvar);
+      array_needs_making = 0;
     }
-  else
-    return (EXECUTION_FAILURE);
-}
 
-$BUILTIN pushd
-$FUNCTION pushd_builtin
-$DEPENDS_ON PUSHD_AND_POPD
-$SHORT_DOC pushd [dir | +n | -n]
-Adds a directory to the top of the directory stack, or rotates
-the stack, making the new top of the stack the current working
-directory.  With no arguments, exchanges the top two directories.
+  setpwd (dirname);
 
-+n     Rotates the stack so that the Nth directory (counting
-       from the left of the list shown by `dirs') is at the top.
+  if (dirname && dirname != the_current_working_directory)
+    free (dirname);
 
--n     Rotates the stack so that the Nth directory (counting
-       from the right) is at the top.
-
-dir    adds DIR to the directory stack at the top, making it the
-       new current working directory.
-
-You can see the directory stack with the `dirs' command.
-$END
-
-#if defined (PUSHD_AND_POPD)
-/* Some useful commands whose behaviour has been observed in Csh. */
-
-/* The list of remembered directories. */
-static char **pushd_directory_list = (char **)NULL;
+  return (r);
+}
 
-/* Number of existing slots in this list. */
-static int directory_list_size = 0;
+/* Call get_working_directory to reset the value of
+   the_current_working_directory () */
+static char *
+resetpwd (caller)
+     char *caller;
+{
+  char *tdir;
+      
+  FREE (the_current_working_directory);
+  the_current_working_directory = (char *)NULL;
+  tdir = get_working_directory (caller);
+  return (tdir);
+}
 
-/* Offset to the end of the list. */
-static int directory_list_offset = 0;
+#define LCD_DOVARS     0x001
+#define LCD_DOSPELL    0x002
+#define LCD_PRINTPATH  0x004
+#define LCD_FREEDIRNAME        0x010
 
-pushd_builtin (list)
+/* This builtin is ultimately the way that all user-visible commands should
+   change the current working directory.  It is called by cd_to_string (),
+   so the programming interface is simple, and it handles errors and
+   restrictions properly. */
+int
+cd_builtin (list)
      WORD_LIST *list;
 {
-  char *temp, *current_directory;
-  int j = directory_list_offset - 1;
-  char direction = '+';
+  char *dirname, *cdpath, *path, *temp;
+  int path_index, no_symlinks, opt, lflag;
 
-  /* If there is no argument list then switch current and
-     top of list. */
-  if (!list)
+#if defined (RESTRICTED_SHELL)
+  if (restricted)
     {
-      if (!directory_list_offset)
-       {
-         builtin_error ("No other directory");
-         return (EXECUTION_FAILURE);
-       }
-
-      current_directory = get_working_directory ("pushd");
-      if (!current_directory)
-       return (EXECUTION_FAILURE);
-
-      temp = pushd_directory_list[j];
-      pushd_directory_list[j] = current_directory;
-      goto change_to_temp;
+      sh_restricted ((char *)NULL);
+      return (EXECUTION_FAILURE);
     }
-  else
+#endif /* RESTRICTED_SHELL */
+
+  no_symlinks = no_symbolic_links;
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "LP")) != -1)
     {
-      direction = *(list->word->word);
-      if (direction == '+' || direction == '-')
+      switch (opt)
        {
-         int num;
-         if (1 == sscanf (&(list->word->word)[1], "%d", &num))
-           {
-             if (direction == '-')
-               num = directory_list_offset - num;
-
-             if (num > directory_list_offset || num < 0)
-               {
-                 if (!directory_list_offset)
-                   builtin_error ("Directory stack empty");
-                 else
-                   builtin_error ("Stack contains only %d directories",
-                                   directory_list_offset + 1);
-                 return (EXECUTION_FAILURE);
-               }
-             else
-               {
-                 /* Rotate the stack num times.  Remember, the
-                    current directory acts like it is part of the
-                    stack. */
-                 temp = get_working_directory ("pushd");
-
-                 if (!num)
-                   goto change_to_temp;
-
-                 do
-                   {
-                     char *top =
-                       pushd_directory_list[directory_list_offset - 1];
-
-                     for (j = directory_list_offset - 2; j > -1; j--)
-                       pushd_directory_list[j + 1] = pushd_directory_list[j];
-
-                     pushd_directory_list[j + 1] = temp;
-
-                     temp = top;
-                     num--;
-                   }
-                 while (num);
-
-                 temp = savestring (temp);
-               change_to_temp:
-                 {
-                   int tt = EXECUTION_FAILURE;
-
-                   if (temp)
-                     {
-                       tt = cd_to_string (temp);
-                       free (temp);
-                     }
-
-                   if ((tt == EXECUTION_SUCCESS))
-                     dirs_builtin ((WORD_LIST *)NULL);
-
-                   return (tt);
-                 }
-               }
-           }
+       case 'P':
+         no_symlinks = 1;
+         break;
+       case 'L':
+         no_symlinks = 0;
+         break;
+       default:
+         builtin_usage ();
+         return (EXECUTION_FAILURE);
        }
+    }
+  list = loptend;
 
-      /* Change to the directory in list->word->word.  Save the current
-        directory on the top of the stack. */
-      current_directory = get_working_directory ("pushd");
-      if (!current_directory)
-       return (EXECUTION_FAILURE);
-
-      if (cd_builtin (list) == EXECUTION_SUCCESS)
-       {
-         if (directory_list_offset == directory_list_size)
-           {
-             pushd_directory_list = (char **)
-               xrealloc (pushd_directory_list,
-                         (directory_list_size += 10) * sizeof (char *));
-           }
-         pushd_directory_list[directory_list_offset++] = current_directory;
+  lflag = (cdable_vars ? LCD_DOVARS : 0) |
+         ((interactive && cdspelling) ? LCD_DOSPELL : 0);
 
-         dirs_builtin ((WORD_LIST *)NULL);
+  if (list == 0)
+    {
+      /* `cd' without arguments is equivalent to `cd $HOME' */
+      dirname = get_string_value ("HOME");
 
-         return (EXECUTION_SUCCESS);
-       }
-      else
+      if (dirname == 0)
        {
-         free (current_directory);
+         builtin_error (_("HOME not set"));
          return (EXECUTION_FAILURE);
        }
+      lflag = 0;
     }
-}
-#endif /* PUSHD_AND_POPD */
-
-$BUILTIN dirs
-$FUNCTION dirs_builtin
-$DEPENDS_ON PUSHD_AND_POPD
-$SHORT_DOC dirs [-l]
-Display the list of currently remembered directories.  Directories
-find their way onto the list with the `pushd' command; you can get
-back up through the list with the `popd' command.
-
-The -l flag specifies that `dirs' should not print shorthand versions
-of directories which are relative to your home directory.  This means
-that `~/bin' might be displayed as `/homes/bfox/bin'.
-$END
-
-#if defined (PUSHD_AND_POPD)
-/* Print the current list of directories on the directory stack. */
-dirs_builtin (list)
-     WORD_LIST *list;
-{
-  int i, format, desired_index, index_flag;
-  char *temp, *w;
-
-  format = index_flag = 0;
-  desired_index = -1;
-  /* Maybe do long form or print specific dir stack entry? */
-  while (list)
+  else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
     {
-      if (strcmp (list->word->word, "-l") == 0)
-       {
-         format++;
-         list = list->next;
-       }
-      else if (*list->word->word == '+' && all_digits (list->word->word + 1))
+      /* This is `cd -', equivalent to `cd $OLDPWD' */
+      dirname = get_string_value ("OLDPWD");
+
+      if (dirname == 0)
        {
-         w = list->word->word + 1;
-         index_flag = 1;
-         i = atoi (w);
-         /* dirs +0 prints the current working directory. */
-         if (i == 0)
-           desired_index = i;
-         else if (i == directory_list_offset)
-           {
-             desired_index = 0;
-             index_flag = 2;
-           }
-         else
-           desired_index = directory_list_offset - i;
-         list = list->next;
+         builtin_error (_("OLDPWD not set"));
+         return (EXECUTION_FAILURE);
        }
-      else if (*list->word->word == '-' && all_digits (list->word->word + 1))
+#if 0
+      lflag = interactive ? LCD_PRINTPATH : 0;
+#else
+      lflag = LCD_PRINTPATH;           /* According to SUSv3 */
+#endif
+    }
+  else if (absolute_pathname (list->word->word))
+    dirname = list->word->word;
+  else if (privileged_mode == 0 && (cdpath = get_string_value ("CDPATH")))
+    {
+      dirname = list->word->word;
+
+      /* Find directory in $CDPATH. */
+      path_index = 0;
+      while (path = extract_colon_unit (cdpath, &path_index))
        {
-         w = list->word->word + 1;
-         i = atoi (w);
-         index_flag = 2;
-         /* dirs -X where X is directory_list_offset prints the current
-            working directory. */
-         if (i == directory_list_offset)
+         /* OPT is 1 if the path element is non-empty */
+         opt = path[0] != '\0';
+         temp = sh_makepath (path, dirname, MP_DOTILDE);
+         free (path);
+
+         if (change_to_directory (temp, no_symlinks))
            {
-             index_flag = 1;
-             desired_index = 0;
+             /* POSIX.2 says that if a nonempty directory from CDPATH
+                is used to find the directory to change to, the new
+                directory name is echoed to stdout, whether or not
+                the shell is interactive. */
+             if (opt && (path = no_symlinks ? temp : the_current_working_directory))
+               printf ("%s\n", path);
+
+             free (temp);
+#if 0
+             /* Posix.2 says that after using CDPATH, the resultant
+                value of $PWD will not contain `.' or `..'. */
+             return (bindpwd (posixly_correct || no_symlinks));
+#else
+             return (bindpwd (no_symlinks));
+#endif
            }
          else
-           desired_index = i;
-         list = list->next;
+           free (temp);
        }
-      else
+
+      /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't
+        try the current directory, so we just punt now with an error
+        message if POSIXLY_CORRECT is non-zero.  The check for cdpath[0]
+        is so we don't mistakenly treat a CDPATH value of "" as not
+        specifying the current directory. */
+      if (posixly_correct && cdpath[0])
        {
-         bad_option (list->word->word);
+         builtin_error ("%s: %s", dirname, strerror (ENOENT));
          return (EXECUTION_FAILURE);
        }
     }
+  else
+    dirname = list->word->word;
 
-  if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
+  /* When we get here, DIRNAME is the directory to change to.  If we
+     chdir successfully, just return. */
+  if (change_to_directory (dirname, no_symlinks))
     {
-      if (directory_list_offset == 0)
-       builtin_error ("directory stack empty");
-      else
-       builtin_error ("%s: bad directory stack index", w);
-      return (EXECUTION_FAILURE);
+      if (lflag & LCD_PRINTPATH)
+       printf ("%s\n", dirname);
+      return (bindpwd (no_symlinks));
     }
 
-  /* The first directory printed is always the current working directory. */
-  if (!index_flag || (index_flag == 1 && desired_index == 0))
+  /* If the user requests it, then perhaps this is the name of
+     a shell variable, whose value contains the directory to
+     change to. */
+  if (lflag & LCD_DOVARS)
     {
-      temp = get_working_directory ("dirs");
-      if (!temp)
-       temp = savestring ("<no directory>");
-      printf ("%s", format ? temp : polite_directory_format (temp));
-      free (temp);
-      if (index_flag)
+      temp = get_string_value (dirname);
+      if (temp && change_to_directory (temp, no_symlinks))
        {
-         putchar ('\n');
-         return EXECUTION_SUCCESS;
+         printf ("%s\n", temp);
+         return (bindpwd (no_symlinks));
        }
     }
 
-#define DIRSTACK_ENTRY(i) \
-       format ? pushd_directory_list[i] \
-              : polite_directory_format (pushd_directory_list[i])
-
-  /* Now print the requested directory stack entries. */
-  if (index_flag)
-    printf ("%s", DIRSTACK_ENTRY (desired_index));
-  else
-    for (i = (directory_list_offset - 1); i > -1; i--)
-      printf (" %s", DIRSTACK_ENTRY (i));
+  /* If the user requests it, try to find a directory name similar in
+     spelling to the one requested, in case the user made a simple
+     typo.  This is similar to the UNIX 8th and 9th Edition shells. */
+  if (lflag & LCD_DOSPELL)
+    {
+      temp = dirspell (dirname);
+      if (temp && change_to_directory (temp, no_symlinks))
+       {
+         printf ("%s\n", temp);
+         return (bindpwd (no_symlinks));
+       }
+      else
+       FREE (temp);
+    }
 
-  putchar ('\n');
-  fflush (stdout);
-  return (EXECUTION_SUCCESS);
+  builtin_error ("%s: %s", dirname, strerror (errno));
+  return (EXECUTION_FAILURE);
 }
-#endif /* PUSHD_AND_POPD */
 
-$BUILTIN popd
-$FUNCTION popd_builtin
-$DEPENDS_ON PUSHD_AND_POPD
-$SHORT_DOC popd [+n | -n]
-Removes entries from the directory stack.  With no arguments,
-removes the top directory from the stack, and cd's to the new
-top directory.
+$BUILTIN pwd
+$FUNCTION pwd_builtin
+$SHORT_DOC pwd [-LP]
+Print the name of the current working directory.
 
-+n     removes the Nth entry counting from the left of the list
-       shown by `dirs', starting with zero.  For example: `popd +0'
-       removes the first directory, `popd +1' the second.
+Options:
+  -L   print the value of $PWD if it names the current working
+       directory
+  -P   print the physical directory, without any symbolic links
 
--n     removes the Nth entry counting from the right of the list
-       shown by `dirs', starting with zero.  For example: `popd -0'
-       removes the last directory, `popd -1' the next to last.
+By default, `pwd' behaves as if `-L' were specified.
 
-You can see the directory stack with the `dirs' command.
+Exit Status:
+Returns 0 unless an invalid option is given or the current directory
+cannot be read.
 $END
 
-#if defined (PUSHD_AND_POPD)
-/* Pop the directory stack, and then change to the new top of the stack.
-   If LIST is non-null it should consist of a word +N or -N, which says
-   what element to delete from the stack.  The default is the top one. */
-popd_builtin (list)
+/* Non-zero means that pwd always prints the physical directory, without
+   symbolic links. */
+static int verbatim_pwd;
+
+/* Print the name of the current working directory. */
+int
+pwd_builtin (list)
      WORD_LIST *list;
 {
-  register int i;
-  int which = 0;
-  char direction = '+';
+  char *directory;
+  int opt, pflag;
 
-  if (list)
+  verbatim_pwd = no_symbolic_links;
+  pflag = 0;
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "LP")) != -1)
     {
-      direction = *(list->word->word);
-
-      if ((direction != '+' && direction != '-') ||
-         (1 != sscanf (&((list->word->word)[1]), "%d", &which)))
+      switch (opt)
        {
-         builtin_error ("bad arg `%s'", list->word->word);
+       case 'P':
+         verbatim_pwd = pflag = 1;
+         break;
+       case 'L':
+         verbatim_pwd = 0;
+         break;
+       default:
+         builtin_usage ();
          return (EXECUTION_FAILURE);
        }
     }
+  list = loptend;
 
-  if (which > directory_list_offset || (!directory_list_offset && !which))
-    {
-      if (!directory_list_offset)
-       builtin_error ("Directory stack empty");
-      else
-       builtin_error ("Stack contains only %d directories",
-                       directory_list_offset + 1);
-      return (EXECUTION_FAILURE);
-    }
+#define tcwd the_current_working_directory
 
-  /* Handle case of no specification, or top of stack specification. */
-  if ((direction == '+' && which == 0) ||
-      (direction == '-' && which == directory_list_offset))
-    {
-      i = cd_to_string (pushd_directory_list[directory_list_offset - 1]);
-      if (i != EXECUTION_SUCCESS)
-       return (i);
-      free (pushd_directory_list[--directory_list_offset]);
-    }
-  else
-    {
-      /* Since an offset other than the top directory was specified,
-        remove that directory from the list and shift the remainder
-        of the list into place. */
+  directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd)
+                  : get_working_directory ("pwd");
 
-      if (direction == '+')
-       i = directory_list_offset - which;
-      else
-       i = which;
+  /* Try again using getcwd() if canonicalization fails (for instance, if
+     the file system has changed state underneath bash). */
+  if ((tcwd && directory == 0) ||
+      (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0))
+    directory = resetpwd ("pwd");
 
-      free (pushd_directory_list[i]);
-      directory_list_offset--;
+#undef tcwd
 
-      /* Shift the remainder of the list into place. */
-      for (; i < directory_list_offset; i++)
-       pushd_directory_list[i] = pushd_directory_list[i + 1];
+  if (directory)
+    {
+      printf ("%s\n", directory);
+      /* This is dumb but posix-mandated. */
+      if (posixly_correct && pflag)
+       setpwd (directory);
+      if (directory != the_current_working_directory)
+       free (directory);
+      return (sh_chkwrite (EXECUTION_SUCCESS));
     }
-
-  dirs_builtin ((WORD_LIST *)NULL);
-
-  return (EXECUTION_SUCCESS);
+  else
+    return (EXECUTION_FAILURE);
 }
-#endif /* PUSHD_AND_POPD */
 
 /* Do the work of changing to the directory NEWDIR.  Handle symbolic
-   link following, etc. */
+   link following, etc.  This function *must* return with
+   the_current_working_directory either set to NULL (in which case
+   getcwd() will eventually be called), or set to a string corresponding
+   to the working directory.  Return 1 on success, 0 on failure. */
 
 static int
-change_to_directory (newdir)
+change_to_directory (newdir, nolinks)
      char *newdir;
+     int nolinks;
 {
-  char *t;
+  char *t, *tdir;
+  int err, canon_failed, r, ndlen, dlen;
+
+  tdir = (char *)NULL;
 
-  if (!no_symbolic_links)
+  if (the_current_working_directory == 0)
     {
-      int chdir_return = 0;
-      char *tdir = (char *)NULL;
+      t = get_working_directory ("chdir");
+      FREE (t);
+    }
 
-      if (!the_current_working_directory)
-       {
-         t = get_working_directory ("cd_links");
-         FREE (t);
-       }
+  t = make_absolute (newdir, the_current_working_directory);
 
-      if (the_current_working_directory)
-       t = make_absolute (newdir, the_current_working_directory);
-      else
-       t = savestring (newdir);
+  /* TDIR is either the canonicalized absolute pathname of NEWDIR
+     (nolinks == 0) or the absolute physical pathname of NEWDIR
+     (nolinks != 0). */
+  tdir = nolinks ? sh_physpath (t, 0)
+                : sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
 
-      /* TDIR is the canonicalized absolute pathname of the NEWDIR. */
-      tdir = canonicalize_pathname (t);
+  ndlen = strlen (newdir);
+  dlen = strlen (t);
 
-      /* Use the canonicalized version of NEWDIR, or, if canonicalization
-        failed, use the non-canonical form. */
-      if (tdir && *tdir)
-       free (t);
-      else
-       {
-         FREE (tdir);
+  /* Use the canonicalized version of NEWDIR, or, if canonicalization
+     failed, use the non-canonical form. */
+  canon_failed = 0;
+  if (tdir && *tdir)
+    free (t);
+  else
+    {
+      FREE (tdir);
+      tdir = t;
+      canon_failed = 1;
+    }
 
-         tdir = t;
-       }
+  /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath
+     returns NULL (because it checks the path, it will return NULL if the
+     resolved path doesn't exist), fail immediately. */
+  if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX))
+    {
+#if defined ENAMETOOLONG
+      if (errno != ENOENT && errno != ENAMETOOLONG)
+#else
+      if (errno != ENOENT)
+#endif
+       errno = ENOTDIR;
+      free (tdir);
+      return (0);
+    }
 
-      if (chdir (tdir) < 0)
+  /* If the chdir succeeds, update the_current_working_directory. */
+  if (chdir (nolinks ? newdir : tdir) == 0)
+    {
+      /* If canonicalization failed, but the chdir succeeded, reset the
+        shell's idea of the_current_working_directory. */
+      if (canon_failed)
        {
-         int err;
+         t = resetpwd ("cd");
+         if (t == 0)
+           set_working_directory (tdir);
+       }
+      else
+       set_working_directory (tdir);
 
-         chdir_return = 0;
-         free (tdir);
+      free (tdir);
+      return (1);
+    }
 
-         err = errno;
+  /* We failed to change to the appropriate directory name.  If we tried
+     what the user passed (nolinks != 0), punt now. */
+  if (nolinks)
+    {
+      free (tdir);
+      return (0);
+    }
 
-         /* We failed changing to the canonicalized directory name.  Try
-            what the user passed verbatim.  If we succeed, reinitialize
-            the_current_working_directory. */
-         if (chdir (newdir) == 0)
-           {
-             chdir_return = 1;
-             if (the_current_working_directory)
-               {
-                 free (the_current_working_directory);
-                 the_current_working_directory = (char *)NULL;
-               }
-
-             tdir = get_working_directory ("cd");
-             FREE (tdir);
-           }
-         else
-           errno = err;
-       }
-      else
-       {
-         chdir_return = 1;
+  err = errno;
 
-         FREE (the_current_working_directory);
-         the_current_working_directory = tdir;
-       }
+  /* We're not in physical mode (nolinks == 0), but we failed to change to
+     the canonicalized directory name (TDIR).  Try what the user passed
+     verbatim. If we succeed, reinitialize the_current_working_directory. */
+  if (chdir (newdir) == 0)
+    {
+      t = resetpwd ("cd");
+      if (t == 0)
+       set_working_directory (tdir);
+      else
+       free (t);
 
-      return (chdir_return);
+      r = 1;
     }
   else
     {
-      if (chdir (newdir) < 0)
-       return (0);
-      else
-       return (1);
+      errno = err;
+      r = 0;
     }
-}
 
-/* Switch to the directory in NAME.  This uses the cd_builtin to do the work,
-   so if the result is EXECUTION_FAILURE then an error message has already
-   been printed. */
-static int
-cd_to_string (name)
-     char *name;
-{
-  WORD_LIST *tlist = make_word_list (make_word (name), NULL);
-  int result = (cd_builtin (tlist));
-  dispose_words (tlist);
-  return (result);
+  free (tdir);
+  return r;
 }