Imported from ../bash-4.0-rc1.tar.gz.
[platform/upstream/bash.git] / builtins / cd.def
index 4bbad7e..d53b258 100644 (file)
@@ -1,46 +1,51 @@
 This file is cd.def, from which is created cd.c.  It implements the
 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>
 
 #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"
+#include "posixdir.h"
+#include "posixstat.h"
+#ifndef _MINIX
 #include <sys/param.h>
+#endif
 
 #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"
 
@@ -48,14 +53,14 @@ $PRODUCES cd.c
 extern int errno;
 #endif /* !errno */
 
-extern int posixly_correct, interactive;
+extern int posixly_correct;
 extern int array_needs_making;
-extern char *bash_getcwd_errstr;
-
-static int change_to_directory ();
+extern const char * const bash_getcwd_errstr;
 
-static char *cdspell ();
-static int spname (), mindist (), spdist ();
+static int bindpwd __P((int));
+static void setpwd __P((char *));
+static char *resetpwd __P((char *));
+static int change_to_directory __P((char *, int));
 
 /* Change this to 1 to get cd spelling correction by default. */
 int cdspelling = 0;
@@ -64,57 +69,47 @@ int cdable_vars;
 
 $BUILTIN cd
 $FUNCTION cd_builtin
-$SHORT_DOC cd [-PL] [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 in CDPATH
-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 option `cdable_vars' is set, then try the word as a variable
-name.  If that variable has a value, then cd to the value of that
-variable.  The -P option says to use the physical directory structure
-instead of following symbolic links; the -L option forces symbolic links
-to be followed.
+$SHORT_DOC cd [-L|-P] [dir]
+Change the shell working directory.
+
+Change the current directory to DIR.  The default DIR is the value of the
+HOME shell variable.
+
+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 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.
+
+Options:
+    -L force symbolic links to be followed
+    -P use the physical directory structure without following symbolic
+       links
+
+The default is to follow symbolic links, as if `-L' were specified.
+
+Exit Status:
+Returns 0 if the directory is changed; non-zero otherwise.
 $END
 
-/* Take PATH, an element from $CDPATH, and DIR, a directory name, and paste
-   them together into PATH/DIR.  Tilde expansion is performed on PATH if
-   DOTILDE is non-zero.  If PATH is the empty string, it is converted to
-   `./', since a null element in $CDPATH means the current directory. */
-static char *
-mkpath (path, dir, dotilde)
-     char *path, *dir;
-     int dotilde;
+/* Just set $PWD, don't change OLDPWD.  Used by `pwd -P' in posix mode. */
+static void
+setpwd (dirname)
+     char *dirname;
 {
-  int dirlen, pathlen;
-  char *ret, *xpath;
-
-  if (*path == '\0')
-    {
-      xpath = xmalloc (2);
-      xpath[0] = '.';
-      xpath[1] = '\0';
-      pathlen = 1;
-    }
-  else
-    {
-      xpath = (dotilde && *path == '~') ? bash_tilde_expand (path) : path;
-      pathlen = strlen (xpath);
-    }
+  int old_anm;
+  SHELL_VAR *tvar;
 
-  dirlen = strlen (dir);
-  ret = xmalloc (2 + dirlen + pathlen);
-  strcpy (ret, xpath);
-  if (xpath[pathlen - 1] != '/')
+  old_anm = array_needs_making;
+  tvar = bind_variable ("PWD", dirname ? dirname : "", 0);
+  if (old_anm == 0 && array_needs_making && exported_p (tvar))
     {
-      ret[pathlen++] = '/';
-      ret[pathlen] = '\0';
+      update_export_env_inplace ("PWD=", 4, dirname ? dirname : "");
+      array_needs_making = 0;
     }
-  strcpy (ret + pathlen, dir);
-  if (xpath != path)
-    free (xpath);
-  return (ret);
 }
 
 static int
@@ -122,41 +117,53 @@ bindpwd (no_symlinks)
      int no_symlinks;
 {
   char *dirname, *pwdvar;
-  int old_symlinks, old_anm;
+  int old_anm, r;
   SHELL_VAR *tvar;
 
-  if (no_symlinks)
-    {
-      old_symlinks = no_symbolic_links;
-      no_symbolic_links = 1;
-      dirname = get_working_directory ("cd");
-      no_symbolic_links = old_symlinks;
-    }
-  else
-    dirname = get_working_directory ("cd");
+  r = sh_chkwrite (EXECUTION_SUCCESS);
 
-  bind_variable ("OLDPWD", get_string_value ("PWD"));
+#define tcwd the_current_working_directory
+  dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd)
+                : get_working_directory ("cd");
+#undef tcwd
 
   old_anm = array_needs_making;
-  tvar = bind_variable ("PWD", dirname);
-  /* This is an efficiency hack.  If PWD is exported, we will need to
-     remake the exported environment every time we change directories.
-     If there is no other reason to make the exported environment, just
-     update PWD in place and mark the exported environment as no longer
-     needing a remake. */
+  pwdvar = get_string_value ("PWD");
+
+  tvar = bind_variable ("OLDPWD", pwdvar, 0);
   if (old_anm == 0 && array_needs_making && exported_p (tvar))
     {
-      pwdvar = xmalloc (strlen (dirname) + 5); /* 5 = "PWD" + '=' + '\0' */
-      strcpy (pwdvar, "PWD=");
-      strcpy (pwdvar + 4, dirname);
-      add_or_supercede_exported_var (pwdvar, 0);
+      update_export_env_inplace ("OLDPWD=", 7, pwdvar);
       array_needs_making = 0;
     }
 
-  FREE (dirname);
-  return (EXECUTION_SUCCESS);
+  setpwd (dirname);
+
+  if (dirname && dirname != the_current_working_directory)
+    free (dirname);
+
+  return (r);
+}
+
+/* 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);
 }
 
+#define LCD_DOVARS     0x001
+#define LCD_DOSPELL    0x002
+#define LCD_PRINTPATH  0x004
+#define LCD_FREEDIRNAME        0x010
+
 /* 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
@@ -166,13 +173,12 @@ cd_builtin (list)
      WORD_LIST *list;
 {
   char *dirname, *cdpath, *path, *temp;
-  int path_index, no_symlinks, opt;
-  struct stat sb;
+  int path_index, no_symlinks, opt, lflag;
 
 #if defined (RESTRICTED_SHELL)
   if (restricted)
     {
-      builtin_error ("restricted");
+      sh_restricted ((char *)NULL);
       return (EXECUTION_FAILURE);
     }
 #endif /* RESTRICTED_SHELL */
@@ -196,6 +202,9 @@ cd_builtin (list)
     }
   list = loptend;
 
+  lflag = (cdable_vars ? LCD_DOVARS : 0) |
+         ((interactive && cdspelling) ? LCD_DOSPELL : 0);
+
   if (list == 0)
     {
       /* `cd' without arguments is equivalent to `cd $HOME' */
@@ -203,110 +212,134 @@ cd_builtin (list)
 
       if (dirname == 0)
        {
-         builtin_error ("HOME not set");
-         return (EXECUTION_FAILURE);
-       }
-
-      if (change_to_directory (dirname, no_symlinks) == 0)
-       {
-         builtin_error ("%s: %s", dirname, strerror (errno));
+         builtin_error (_("HOME not set"));
          return (EXECUTION_FAILURE);
        }
+      lflag = 0;
     }
   else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
     {
       /* This is `cd -', equivalent to `cd $OLDPWD' */
       dirname = get_string_value ("OLDPWD");
 
-      if (dirname == 0 || change_to_directory (dirname, no_symlinks) == 0)
+      if (dirname == 0)
        {
-         if (dirname == 0)
-           builtin_error ("OLDPWD not set");
-         else
-           builtin_error ("%s: %s", dirname, strerror (errno));
+         builtin_error (_("OLDPWD not set"));
          return (EXECUTION_FAILURE);
        }
+#if 0
+      lflag = interactive ? LCD_PRINTPATH : 0;
+#else
+      lflag = LCD_PRINTPATH;           /* According to SUSv3 */
+#endif
     }
-  else
+  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;
 
-      if (absolute_pathname (dirname) == 0 && (cdpath = get_string_value ("CDPATH")))
+      /* Find directory in $CDPATH. */
+      path_index = 0;
+      while (path = extract_colon_unit (cdpath, &path_index))
        {
-         /* Find directory in $CDPATH. */
-         path_index = 0;
-         while ((path = extract_colon_unit (cdpath, &path_index)))
+         /* 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))
            {
-             temp = mkpath (path, dirname, 1);
-             free (path);
-
-             if (stat (temp, &sb) < 0 || S_ISDIR (sb.st_mode) == 0)
-               {
-                 free (temp);
-                 continue;
-               }
-
-             if (change_to_directory (temp, no_symlinks))
-               {
-                 if (temp[0] != '.' || temp[1] != '/')
-                   printf ("%s\n", temp);
-
-                 free (temp);
-                 /* Posix.2 says that after using CDPATH, the resultant
-                    value of $PWD will not contain symlinks. */
-                 return (bindpwd (posixly_correct));
-               }
-             else
-               free (temp);
+             /* 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
+           free (temp);
        }
 
-      if (change_to_directory (dirname, no_symlinks))
-       return (bindpwd (no_symlinks));
-
-      /* 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 (cdable_vars)
+      /* 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])
        {
-         temp = get_string_value (dirname);
-         if (temp && change_to_directory (temp, no_symlinks))
-           {
-             printf ("%s\n", temp);
-             return (bindpwd (no_symlinks));
-           }
+         builtin_error ("%s: %s", dirname, strerror (ENOENT));
+         return (EXECUTION_FAILURE);
        }
+    }
+  else
+    dirname = list->word->word;
+
+  /* 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 (lflag & LCD_PRINTPATH)
+       printf ("%s\n", dirname);
+      return (bindpwd (no_symlinks));
+    }
 
-      /* 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 (interactive && cdspelling)
+  /* 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_string_value (dirname);
+      if (temp && change_to_directory (temp, no_symlinks))
        {
-         temp = cdspell (dirname);
-         if (temp && change_to_directory (temp, no_symlinks))
-            {
-              printf ("%s\n", temp);
-              free (temp);
-              return (bindpwd (no_symlinks));
-            }
-          else
-           FREE (temp);
+         printf ("%s\n", temp);
+         return (bindpwd (no_symlinks));
        }
+    }
 
-      builtin_error ("%s: %s", dirname, strerror (errno));
-      return (EXECUTION_FAILURE);
+  /* 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);
     }
 
-  return (bindpwd (no_symlinks));
+  builtin_error ("%s: %s", dirname, strerror (errno));
+  return (EXECUTION_FAILURE);
 }
 
 $BUILTIN pwd
 $FUNCTION pwd_builtin
-$SHORT_DOC pwd [-PL]
-Print the current working directory.  With the -P option, pwd prints
-the physical directory, without any symbolic links; the -L option
-makes pwd follow symbolic links.
+$SHORT_DOC pwd [-LP]
+Print the name of the current working directory.
+
+Options:
+  -L   print the value of $PWD if it names the current working
+       directory
+  -P   print the physical directory, without any symbolic links
+
+By default, `pwd' behaves as if `-L' were specified.
+
+Exit Status:
+Returns 0 unless an invalid option is given or the current directory
+cannot be read.
 $END
 
 /* Non-zero means that pwd always prints the physical directory, without
@@ -318,17 +351,18 @@ int
 pwd_builtin (list)
      WORD_LIST *list;
 {
-  char *directory, *buffer;
-  int opt;
+  char *directory;
+  int opt, pflag;
 
   verbatim_pwd = no_symbolic_links;
+  pflag = 0;
   reset_internal_getopt ();
   while ((opt = internal_getopt (list, "LP")) != -1)
     {
       switch (opt)
        {
        case 'P':
-         verbatim_pwd = 1;
+         verbatim_pwd = pflag = 1;
          break;
        case 'L':
          verbatim_pwd = 0;
@@ -340,276 +374,140 @@ pwd_builtin (list)
     }
   list = loptend;
 
-  if (verbatim_pwd)
-    {
-      buffer = xmalloc (PATH_MAX);
-      directory = getcwd (buffer, PATH_MAX);
+#define tcwd the_current_working_directory
 
-      if (directory == 0)
-       {
-         builtin_error ("%s: %s", bash_getcwd_errstr, strerror (errno));
-         free (buffer);
-       }
-    }
-  else
-    directory = get_working_directory ("pwd");
+  directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd)
+                  : get_working_directory ("pwd");
+
+  /* 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");
+
+#undef tcwd
 
   if (directory)
     {
       printf ("%s\n", directory);
-      fflush (stdout);
-      free (directory);
-      return (EXECUTION_SUCCESS);
+      /* 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));
     }
   else
     return (EXECUTION_FAILURE);
 }
 
 /* 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, nolinks)
      char *newdir;
      int nolinks;
 {
-  char *t;
+  char *t, *tdir;
+  int err, canon_failed, r, ndlen, dlen;
 
-  if (nolinks == 0)
-    {
-      int chdir_return = 0;
-      char *tdir = (char *)NULL;
+  tdir = (char *)NULL;
 
-      if (the_current_working_directory == 0)
-       {
-         t = get_working_directory ("cd_links");
-         FREE (t);
-       }
-
-      if (the_current_working_directory)
-       t = make_absolute (newdir, the_current_working_directory);
-      else
-       t = savestring (newdir);
-
-      /* TDIR is the canonicalized absolute pathname of the NEWDIR. */
-      tdir = canonicalize_pathname (t);
-
-      /* Use the canonicalized version of NEWDIR, or, if canonicalization
-        failed, use the non-canonical form. */
-      if (tdir && *tdir)
-       free (t);
-      else
-       {
-         FREE (tdir);
-         tdir = t;
-       }
-
-      if (chdir (tdir) < 0)
-       {
-         int err;
-
-         chdir_return = 0;
-         free (tdir);
-
-         err = errno;
-
-         /* 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;
-
-         FREE (the_current_working_directory);
-         the_current_working_directory = tdir;
-       }
-
-      return (chdir_return);
+  if (the_current_working_directory == 0)
+    {
+      t = get_working_directory ("chdir");
+      FREE (t);
     }
-  else
-    return (chdir (newdir) == 0);
-}
 
-/* Code for cd spelling correction.  Original patch submitted by
-   Neil Russel (caret@c-side.com). */
+  t = make_absolute (newdir, the_current_working_directory);
 
-static char *
-cdspell (dirname)
-     char *dirname;
-{
-  int n;
-  char *guess;
+  /* 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);
 
-  n = (strlen (dirname) * 3 + 1) / 2 + 1;
-  guess = xmalloc (n);
+  ndlen = strlen (newdir);
+  dlen = strlen (t);
 
-  switch (spname (dirname, guess))
+  /* Use the canonicalized version of NEWDIR, or, if canonicalization
+     failed, use the non-canonical form. */
+  canon_failed = 0;
+  if (tdir && *tdir)
+    free (t);
+  else
     {
-    case -1:
-    default:
-      free (guess);
-      return (char *)NULL;
-    case 0:
-    case 1:
-      return guess;
+      FREE (tdir);
+      tdir = t;
+      canon_failed = 1;
     }
-}
-
-/*
- * `spname' and its helpers are inspired by the code in "The UNIX
- * Programming Environment, Kernighan & Pike, Prentice-Hall 1984",
- * pages 209 - 213.
- */
-
-/*
- *     `spname' -- return a correctly spelled filename
- *
- *     int spname(char * oldname, char * newname)
- *     Returns:  -1 if no reasonable match found
- *                0 if exact match found
- *                1 if corrected
- *     Stores corrected name in `newname'.
- */
-static int
-spname(oldname, newname)
-     char *oldname;
-     char *newname;
-{
-  char *op, *np, *p;
-  char guess[PATH_MAX + 1], best[PATH_MAX + 1];
 
-  op = oldname;
-  np = newname;
-  for (;;)
+  /* 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))
     {
-      while (*op == '/')    /* Skip slashes */
-       *np++ = *op++;
-      *np = '\0';
-
-      if (*op == '\0')    /* Exact or corrected */
-        {
-         /* `.' is rarely the right thing. */
-         if (oldname[1] == '\0' && newname[1] == '\0' &&
-               oldname[0] != '.' && newname[0] == '.')
-           return -1;
-         return strcmp(oldname, newname) != 0;
-        }
-
-      /* Copy next component into guess */
-      for (p = guess; *op != '/' && *op != '\0'; op++)
-       if (p < guess + PATH_MAX)
-         *p++ = *op;
-      *p = '\0';
-
-      if (mindist(newname, guess, best) >= 3)
-       return -1;  /* Hopeless */
-
-      /*
-       *  Add to end of newname
-       */
-      for (p = best; *np = *p++; np++)
-       ;
+#if defined ENAMETOOLONG
+      if (errno != ENOENT && errno != ENAMETOOLONG)
+#else
+      if (errno != ENOENT)
+#endif
+       errno = ENOTDIR;
+      free (tdir);
+      return (0);
     }
-}
-
-/*
- *  Search directory for a guess
- */
-static int
-mindist(dir, guess, best)
-     char *dir;
-     char *guess;
-     char *best;
-{
-  DIR *fd;
-  struct dirent *dp;
-  int dist, x;
-
-  dist = 3;    /* Worst distance */
-  if (*dir == '\0')
-    dir = ".";
-
-  if ((fd = opendir(dir)) == NULL)
-    return dist;
 
-  while ((dp = readdir(fd)) != NULL)
+  /* If the chdir succeeds, update the_current_working_directory. */
+  if (chdir (nolinks ? newdir : tdir) == 0)
     {
-      /*
-       *  Look for a better guess.  If the new guess is as
-       *  good as the current one, we take it.  This way,
-       *  any single character match will be a better match
-       *  than ".".
-       */
-      x = spdist(dp->d_name, guess);
-      if (x <= dist && x != 3)
+      /* If canonicalization failed, but the chdir succeeded, reset the
+        shell's idea of the_current_working_directory. */
+      if (canon_failed)
        {
-         strcpy(best, dp->d_name);
-         dist = x;
-         if (dist == 0)    /* Exact match */
-           break;
+         t = resetpwd ("cd");
+         if (t == 0)
+           set_working_directory (tdir);
        }
-    }
-  (void)closedir(fd);
+      else
+       set_working_directory (tdir);
 
-  /* Don't return `.' */
-  if (best[0] == '.' && best[1] == '\0')
-    dist = 3;
-  return dist;
-}
+      free (tdir);
+      return (1);
+    }
 
-/*
- *  `spdist' -- return the "distance" between two names.
- *
- *  int spname(char * oldname, char * newname)
- *  Returns:  0 if strings are identical
- *      1 if two characters are transposed
- *      2 if one character is wrong, added or deleted
- *      3 otherwise
- */
-static int
-spdist(cur, new)
-     char *cur, *new;
-{
-  while (*cur == *new)
+  /* We failed to change to the appropriate directory name.  If we tried
+     what the user passed (nolinks != 0), punt now. */
+  if (nolinks)
     {
-      if (*cur == '\0')
-       return 0;    /* Exact match */
-      cur++;
-      new++;
+      free (tdir);
+      return (0);
     }
 
-  if (*cur)
-    {
-      if (*new)
-       {
-         if (cur[1] && new[1] && cur[0] == new[1] && cur[1] == new[0] && strcmp (cur + 2, new + 2) == 0)
-           return 1;  /* Transposition */
+  err = errno;
 
-         if (strcmp (cur + 1, new + 1) == 0)
-           return 2;  /* One character mismatch */
-       }
+  /* 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);
 
-      if (strcmp(&cur[1], &new[0]) == 0)
-       return 2;    /* Extra character */
+      r = 1;
+    }
+  else
+    {
+      errno = err;
+      r = 0;
     }
 
-  if (*new && strcmp(cur, new + 1) == 0)
-    return 2;      /* Missing character */
-
-  return 3;
+  free (tdir);
+  return r;
 }