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
+Software Foundation; either version 2, or (at your option) any later
version.
Bash is distributed in the hope that it will be useful, but WITHOUT ANY
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.
$PRODUCES cd.c
#include <config.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 "../shell.h"
#include "../flags.h"
-#include "../maxpath.h"
+#include "maxpath.h"
#include "common.h"
#include "bashgetopt.h"
static int change_to_directory ();
static char *cdspell ();
-static int spname (), mindist (), spdist ();
/* Change this to 1 to get cd spelling correction by default. */
int cdspelling = 0;
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;
int old_symlinks, old_anm;
SHELL_VAR *tvar;
- if (no_symlinks)
+#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;
+ pwdvar = get_string_value ("PWD");
+
+ tvar = bind_variable ("OLDPWD", pwdvar);
+ if (old_anm == 0 && array_needs_making && exported_p (tvar))
{
- old_symlinks = no_symbolic_links;
- no_symbolic_links = 1;
- dirname = get_working_directory ("cd");
- no_symbolic_links = old_symlinks;
+ update_export_env_inplace ("OLDPWD=", 7, pwdvar);
+ array_needs_making = 0;
}
- else
- dirname = get_working_directory ("cd");
- bind_variable ("OLDPWD", get_string_value ("PWD"));
-
- 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. */
if (old_anm == 0 && array_needs_making && exported_p (tvar))
{
- pwdvar = xmalloc (STRLEN (dirname) + 5); /* 5 = "PWD" + '=' + '\0' */
- strcpy (pwdvar, "PWD=");
- if (dirname)
- strcpy (pwdvar + 4, dirname);
- add_or_supercede_exported_var (pwdvar, 0);
+ update_export_env_inplace ("PWD=", 4, dirname);
array_needs_making = 0;
}
- FREE (dirname);
+ if (dirname && dirname != the_current_working_directory)
+ free (dirname);
return (EXECUTION_SUCCESS);
}
+#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
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)
}
list = loptend;
+ lflag = (cdable_vars ? LCD_DOVARS : 0) |
+ ((interactive && cdspelling) ? LCD_DOSPELL : 0);
+
if (list == 0)
{
/* `cd' without arguments is equivalent to `cd $HOME' */
builtin_error ("HOME not set");
return (EXECUTION_FAILURE);
}
-
- if (change_to_directory (dirname, no_symlinks) == 0)
- {
- builtin_error ("%s: %s", dirname, strerror (errno));
- 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 (interactive)
- printf ("%s\n", dirname);
+ lflag = interactive ? LCD_PRINTPATH : 0;
}
- else
+ else if (absolute_pathname (list->word->word))
+ dirname = list->word->word;
+ else if (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 = 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))
- {
- /* 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)
- printf ("%s\n", the_current_working_directory);
-
- free (temp);
- /* Posix.2 says that after using CDPATH, the resultant
- value of $PWD will not contain symlinks. */
- return (bindpwd (posixly_correct || no_symlinks));
- }
- else
- free (temp);
- }
+ /* OPT is 1 if the path element is non-empty */
+ opt = path[0] != '\0';
+ temp = sh_makepath (path, dirname, MP_DOTILDE);
+ free (path);
- /* 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. */
- if (posixly_correct)
+ if (change_to_directory (temp, no_symlinks))
{
- builtin_error ("%s: %s", dirname, strerror (ENOENT));
- return (EXECUTION_FAILURE);
+ /* 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)
+ printf ("%s\n", no_symlinks ? temp : the_current_working_directory);
+
+ free (temp);
+ /* Posix.2 says that after using CDPATH, the resultant
+ value of $PWD will not contain `.' or `..'. */
+ return (bindpwd (posixly_correct || no_symlinks));
}
+ 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;
- /* 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)
+ /* 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, 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 = cdspell (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
pwd_builtin (list)
WORD_LIST *list;
{
- char *directory, *buffer;
+ char *directory;
int opt;
verbatim_pwd = no_symbolic_links;
}
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");
+#undef tcwd
if (directory)
{
printf ("%s\n", directory);
+ if (directory != the_current_working_directory)
+ free (directory);
fflush (stdout);
- free (directory);
+ if (ferror (stdout))
+ {
+ builtin_error ("write error: %s", strerror (errno));
+ return (EXECUTION_FAILURE);
+ }
return (EXECUTION_SUCCESS);
}
else
}
/* 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;
- 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 == 0)
+ {
+ t = get_working_directory ("chdir");
+ FREE (t);
+ }
- if (the_current_working_directory)
- t = make_absolute (newdir, the_current_working_directory);
- else
- t = savestring (newdir);
+ t = make_absolute (newdir, the_current_working_directory);
- /* TDIR is the canonicalized absolute pathname of the NEWDIR. */
- tdir = canonicalize_pathname (t);
+ /* 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);
- /* 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;
+ /* 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;
+ }
- chdir_return = 0;
- free (tdir);
+ /* If the chdir succeeds, update the_current_working_directory. */
+ if (chdir (nolinks ? newdir : tdir) == 0)
+ {
+ FREE (the_current_working_directory);
+ the_current_working_directory = 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)
+ 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 (tdir);
- 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)
+ {
+ FREE (the_current_working_directory);
+ the_current_working_directory = (char *)NULL;
+ tdir = get_working_directory ("cd");
+ FREE (tdir);
- return (chdir_return);
+ return (1);
}
else
- return (chdir (newdir) == 0);
+ {
+ errno = err;
+ return (0);
+ }
}
/* Code for cd spelling correction. Original patch submitted by
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++)
- ;
- }
-}
-
-/*
- * 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)
- {
- /*
- * 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);
-
- /* Don't return `.' */
- if (best[0] == '.' && best[1] == '\0')
- dist = 3;
- 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;
-}