summaryrefslogtreecommitdiff
path: root/builtins/cd.def
diff options
context:
space:
mode:
Diffstat (limited to 'builtins/cd.def')
-rw-r--r--builtins/cd.def860
1 files changed, 382 insertions, 478 deletions
diff --git a/builtins/cd.def b/builtins/cd.def
index 338f6942..e6611d0d 100644
--- a/builtins/cd.def
+++ b/builtins/cd.def
@@ -1,5 +1,5 @@
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.
@@ -20,15 +20,20 @@ with Bash; see the file COPYING. If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
$PRODUCES cd.c
+#include <config.h>
-#include <stdio.h>
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include "../posixdir.h"
+#include "../posixstat.h"
#include <sys/param.h>
-#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 <errno.h>
#include <tilde/tilde.h>
@@ -37,27 +42,102 @@ $PRODUCES cd.c
#include "../flags.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, interactive;
+extern char *bash_getcwd_errstr;
+
+static int change_to_directory ();
+
+static char *cdspell ();
+static int spname (), mindist (), spdist ();
+int cdspelling = 1;
+
+int cdable_vars;
$BUILTIN cd
$FUNCTION cd_builtin
-$SHORT_DOC cd [dir]
+$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 are
-separated by a colon (:). A null directory name is the same as
+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 variable `cdable_vars' exists, then try the word as a variable
+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.
+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.
$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;
+{
+ 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);
+ }
+
+ dirlen = strlen (dir);
+ ret = xmalloc (2 + dirlen + pathlen);
+ strcpy (ret, xpath);
+ if (xpath[pathlen - 1] != '/')
+ {
+ ret[pathlen++] = '/';
+ ret[pathlen] = '\0';
+ }
+ strcpy (ret + pathlen, dir);
+ if (xpath != path)
+ free (xpath);
+ return (ret);
+}
+
+static int
+bindpwd (no_symlinks)
+ int no_symlinks;
+{
+ char *dirname;
+ int old_symlinks;
+
+ 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");
+
+ bind_variable ("OLDPWD", get_string_value ("PWD"));
+ bind_variable ("PWD", dirname);
+
+ FREE (dirname);
+ return (EXECUTION_SUCCESS);
+}
+
/* 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
@@ -66,7 +146,9 @@ int
cd_builtin (list)
WORD_LIST *list;
{
- char *dirname;
+ char *dirname, *cdpath, *path, *temp;
+ int path_index, no_symlinks, opt;
+ struct stat sb;
#if defined (RESTRICTED_SHELL)
if (restricted)
@@ -76,535 +158,210 @@ cd_builtin (list)
}
#endif /* RESTRICTED_SHELL */
- if (list)
+ no_symlinks = no_symbolic_links;
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, "LP")) != -1)
{
- char *extract_colon_unit ();
- char *path_string = get_string_value ("CDPATH");
- char *path;
- int path_index = 0, dirlen, pathlen;
-
- dirname = list->word->word;
-
- if (path_string && !absolute_pathname (dirname))
+ switch (opt)
{
- 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);
-
- if (t && change_to_directory (t))
- {
- printf ("%s\n", t);
- goto bind_and_exit;
- }
- }
-
- file_error (dirname);
+ case 'P':
+ no_symlinks = 1;
+ break;
+ case 'L':
+ no_symlinks = 0;
+ break;
+ default:
+ builtin_usage ();
return (EXECUTION_FAILURE);
}
- goto bind_and_exit;
}
- else
+ list = loptend;
+
+ if (list == 0)
{
+ /* `cd' without arguments is equivalent to `cd $HOME' */
dirname = get_string_value ("HOME");
- if (!dirname)
- return (EXECUTION_FAILURE);
-
- if (!change_to_directory (dirname))
+ if (dirname == 0)
{
- file_error (dirname);
+ builtin_error ("HOME not set");
return (EXECUTION_FAILURE);
}
- bind_and_exit:
- {
- char *directory;
-
- directory = get_working_directory ("cd");
-
- bind_variable ("OLDPWD", get_string_value ("PWD"));
- bind_variable ("PWD", directory);
-
- FREE (directory);
- }
- return (EXECUTION_SUCCESS);
- }
-}
-
-$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;
-{
- char *directory, *s;
-
-#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
-
- if (verbatim_pwd)
- {
- char *buffer = xmalloc (MAXPATHLEN);
- directory = getwd (buffer);
-
- if (!directory)
+ if (change_to_directory (dirname, no_symlinks) == 0)
{
- builtin_error ("%s", buffer);
- free (buffer);
+ builtin_error ("%s: %s", dirname, strerror (errno));
+ return (EXECUTION_FAILURE);
}
}
- else
- directory = get_working_directory ("pwd");
-
- if (directory)
+ else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
{
- printf ("%s\n", directory);
- fflush (stdout);
- free (directory);
- return (EXECUTION_SUCCESS);
- }
- 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.
-
-+n Rotates the stack so that the Nth directory (counting
- from the left of the list shown by `dirs') is at the top.
-
--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.
+ /* This is `cd -', equivalent to `cd $OLDPWD' */
+ dirname = get_string_value ("OLDPWD");
-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;
-
-/* Number of existing slots in this list. */
-static int directory_list_size = 0;
-
-/* Offset to the end of the list. */
-static int directory_list_offset = 0;
-
-pushd_builtin (list)
- WORD_LIST *list;
-{
- char *temp, *current_directory;
- int j = directory_list_offset - 1;
- char direction = '+';
-
- /* If there is no argument list then switch current and
- top of list. */
- if (!list)
- {
- if (!directory_list_offset)
+ if (dirname == 0 || change_to_directory (dirname, no_symlinks) == 0)
{
- builtin_error ("No other directory");
+ if (dirname == 0)
+ builtin_error ("OLDPWD not set");
+ else
+ builtin_error ("%s: %s", dirname, strerror (errno));
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;
}
else
{
- direction = *(list->word->word);
- if (direction == '+' || direction == '-')
+ dirname = list->word->word;
+
+ if (absolute_pathname (dirname) == 0 && (cdpath = get_string_value ("CDPATH")))
{
- int num;
- if (1 == sscanf (&(list->word->word)[1], "%d", &num))
+ /* Find directory in $CDPATH. */
+ path_index = 0;
+ while ((path = extract_colon_unit (cdpath, &path_index)))
{
- if (direction == '-')
- num = directory_list_offset - num;
+ temp = mkpath (path, dirname, 1);
+ free (path);
- if (num > directory_list_offset || num < 0)
+ if (stat (temp, &sb) < 0 || S_ISDIR (sb.st_mode) == 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);
+ free (temp);
+ continue;
}
- 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);
+ if (change_to_directory (temp, no_symlinks))
+ {
+ if (temp[0] != '.' || temp[1] != '/')
+ printf ("%s\n", temp);
- return (tt);
- }
+ 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);
}
}
- /* 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 (change_to_directory (dirname, no_symlinks))
+ return (bindpwd (no_symlinks));
- if (cd_builtin (list) == EXECUTION_SUCCESS)
+ /* 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)
{
- if (directory_list_offset == directory_list_size)
+ temp = get_string_value (dirname);
+ if (temp && change_to_directory (temp, no_symlinks))
{
- pushd_directory_list = (char **)
- xrealloc (pushd_directory_list,
- (directory_list_size += 10) * sizeof (char *));
+ printf ("%s\n", temp);
+ return (bindpwd (no_symlinks));
}
- pushd_directory_list[directory_list_offset++] = current_directory;
-
- dirs_builtin ((WORD_LIST *)NULL);
-
- return (EXECUTION_SUCCESS);
- }
- else
- {
- free (current_directory);
- return (EXECUTION_FAILURE);
}
- }
-}
-#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)
- {
- if (strcmp (list->word->word, "-l") == 0)
- {
- format++;
- list = list->next;
- }
- else if (*list->word->word == '+' && all_digits (list->word->word + 1))
+ /* 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)
{
- 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;
+ temp = cdspell (dirname);
+ if (temp && change_to_directory (temp, no_symlinks))
+ {
+ printf ("%s\n", temp);
+ free (temp);
+ return (bindpwd (no_symlinks));
+ }
+ else
+ FREE (temp);
}
- else if (*list->word->word == '-' && all_digits (list->word->word + 1))
- {
- 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)
- {
- index_flag = 1;
- desired_index = 0;
- }
- else
- desired_index = i;
- list = list->next;
- }
- else
- {
- bad_option (list->word->word);
- return (EXECUTION_FAILURE);
- }
- }
- if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
- {
- if (directory_list_offset == 0)
- builtin_error ("directory stack empty");
- else
- builtin_error ("%s: bad directory stack index", w);
+ builtin_error ("%s: %s", dirname, strerror (errno));
return (EXECUTION_FAILURE);
}
- /* The first directory printed is always the current working directory. */
- if (!index_flag || (index_flag == 1 && desired_index == 0))
- {
- temp = get_working_directory ("dirs");
- if (!temp)
- temp = savestring ("<no directory>");
- printf ("%s", format ? temp : polite_directory_format (temp));
- free (temp);
- if (index_flag)
- {
- putchar ('\n');
- return EXECUTION_SUCCESS;
- }
- }
-
-#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));
-
- putchar ('\n');
- fflush (stdout);
- return (EXECUTION_SUCCESS);
+ return (bindpwd (no_symlinks));
}
-#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.
-+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.
-
--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.
-
-You can see the directory stack with the `dirs' command.
+$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.
$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, *buffer;
+ int opt;
- if (list)
+ verbatim_pwd = no_symbolic_links;
+ 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 = 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 (verbatim_pwd)
{
- if (!directory_list_offset)
- builtin_error ("Directory stack empty");
- else
- builtin_error ("Stack contains only %d directories",
- directory_list_offset + 1);
- return (EXECUTION_FAILURE);
- }
+ buffer = xmalloc (PATH_MAX);
+ directory = getcwd (buffer, PATH_MAX);
- /* 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]);
+ if (directory == 0)
+ {
+ builtin_error ("%s: %s", bash_getcwd_errstr, strerror (errno));
+ free (buffer);
+ }
}
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. */
-
- if (direction == '+')
- i = directory_list_offset - which;
- else
- i = which;
-
- free (pushd_directory_list[i]);
- directory_list_offset--;
+ directory = get_working_directory ("pwd");
- /* 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);
+ fflush (stdout);
+ free (directory);
+ return (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. */
static int
-change_to_directory (newdir)
+change_to_directory (newdir, nolinks)
char *newdir;
+ int nolinks;
{
char *t;
- if (!no_symbolic_links)
+ if (nolinks == 0)
{
int chdir_return = 0;
char *tdir = (char *)NULL;
- if (!the_current_working_directory)
+ if (the_current_working_directory == 0)
{
t = get_working_directory ("cd_links");
FREE (t);
@@ -625,7 +382,6 @@ change_to_directory (newdir)
else
{
FREE (tdir);
-
tdir = t;
}
@@ -667,23 +423,171 @@ change_to_directory (newdir)
return (chdir_return);
}
else
+ return (chdir (newdir) == 0);
+}
+
+/* Code for cd spelling correction. Original patch submitted by
+ Neil Russel (caret@c-side.com). */
+
+static char *
+cdspell (dirname)
+ char *dirname;
+{
+ int n;
+ char *guess;
+
+ n = (strlen (dirname) * 3 + 1) / 2 + 1;
+ guess = xmalloc (n);
+
+ switch (spname (dirname, guess))
{
- if (chdir (newdir) < 0)
- return (0);
- else
- return (1);
+ case -1:
+ default:
+ free (guess);
+ return (char *)NULL;
+ case 0:
+ case 1:
+ return guess;
+ }
+}
+
+/*
+ * `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 (;;)
+ {
+ 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++)
+ ;
}
}
-/* 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. */
+/*
+ * Search directory for a guess
+ */
static int
-cd_to_string (name)
- char *name;
+mindist(dir, guess, best)
+ char *dir;
+ char *guess;
+ char *best;
{
- WORD_LIST *tlist = make_word_list (make_word (name), NULL);
- int result = (cd_builtin (tlist));
- dispose_words (tlist);
- return (result);
+ 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)
+ {
+ /*
+ * 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)
+ {
+ strcpy(best, dp->d_name);
+ dist = x;
+ if (dist == 0) /* Exact match */
+ break;
+ }
+ }
+ (void)closedir(fd);
+
+ return dist;
+}
+
+/*
+ * `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)
+ {
+ if (*cur == '\0')
+ return 0; /* Exact match */
+ cur++;
+ new++;
+ }
+
+ 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 */
+
+ if (strcmp (cur + 1, new + 1) == 0)
+ return 2; /* One character mismatch */
+ }
+
+ if (strcmp(&cur[1], &new[0]) == 0)
+ return 2; /* Extra character */
+ }
+
+ if (*new && strcmp(cur, new + 1) == 0)
+ return 2; /* Missing character */
+
+ return 3;
}