615 lines
14 KiB
Modula-2
615 lines
14 KiB
Modula-2
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.
|
|
|
|
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 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.
|
|
|
|
$PRODUCES cd.c
|
|
#include <config.h>
|
|
|
|
#if defined (HAVE_UNISTD_H)
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "../bashtypes.h"
|
|
#include "../posixdir.h"
|
|
#include "../posixstat.h"
|
|
#include <sys/param.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "../bashansi.h"
|
|
|
|
#include <errno.h>
|
|
#include <tilde/tilde.h>
|
|
|
|
#include "../shell.h"
|
|
#include "../flags.h"
|
|
#include "../maxpath.h"
|
|
#include "common.h"
|
|
#include "bashgetopt.h"
|
|
|
|
#if !defined (errno)
|
|
extern int errno;
|
|
#endif /* !errno */
|
|
|
|
extern int posixly_correct, interactive;
|
|
extern int array_needs_making;
|
|
extern char *bash_getcwd_errstr;
|
|
|
|
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;
|
|
|
|
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.
|
|
$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, *pwdvar;
|
|
int old_symlinks, old_anm;
|
|
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");
|
|
|
|
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=");
|
|
strcpy (pwdvar + 4, dirname);
|
|
add_or_supercede_exported_var (pwdvar, 0);
|
|
array_needs_making = 0;
|
|
}
|
|
|
|
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
|
|
restrictions properly. */
|
|
int
|
|
cd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *dirname, *cdpath, *path, *temp;
|
|
int path_index, no_symlinks, opt;
|
|
struct stat sb;
|
|
|
|
#if defined (RESTRICTED_SHELL)
|
|
if (restricted)
|
|
{
|
|
builtin_error ("restricted");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
#endif /* RESTRICTED_SHELL */
|
|
|
|
no_symlinks = no_symbolic_links;
|
|
reset_internal_getopt ();
|
|
while ((opt = internal_getopt (list, "LP")) != -1)
|
|
{
|
|
switch (opt)
|
|
{
|
|
case 'P':
|
|
no_symlinks = 1;
|
|
break;
|
|
case 'L':
|
|
no_symlinks = 0;
|
|
break;
|
|
default:
|
|
builtin_usage ();
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
list = loptend;
|
|
|
|
if (list == 0)
|
|
{
|
|
/* `cd' without arguments is equivalent to `cd $HOME' */
|
|
dirname = get_string_value ("HOME");
|
|
|
|
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));
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
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)
|
|
builtin_error ("OLDPWD not set");
|
|
else
|
|
builtin_error ("%s: %s", dirname, strerror (errno));
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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)))
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
temp = get_string_value (dirname);
|
|
if (temp && change_to_directory (temp, no_symlinks))
|
|
{
|
|
printf ("%s\n", temp);
|
|
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)
|
|
{
|
|
temp = cdspell (dirname);
|
|
if (temp && change_to_directory (temp, no_symlinks))
|
|
{
|
|
printf ("%s\n", temp);
|
|
free (temp);
|
|
return (bindpwd (no_symlinks));
|
|
}
|
|
else
|
|
FREE (temp);
|
|
}
|
|
|
|
builtin_error ("%s: %s", dirname, strerror (errno));
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
return (bindpwd (no_symlinks));
|
|
}
|
|
|
|
$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
|
|
|
|
/* 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;
|
|
{
|
|
char *directory, *buffer;
|
|
int opt;
|
|
|
|
verbatim_pwd = no_symbolic_links;
|
|
reset_internal_getopt ();
|
|
while ((opt = internal_getopt (list, "LP")) != -1)
|
|
{
|
|
switch (opt)
|
|
{
|
|
case 'P':
|
|
verbatim_pwd = 1;
|
|
break;
|
|
case 'L':
|
|
verbatim_pwd = 0;
|
|
break;
|
|
default:
|
|
builtin_usage ();
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
list = loptend;
|
|
|
|
if (verbatim_pwd)
|
|
{
|
|
buffer = xmalloc (PATH_MAX);
|
|
directory = getcwd (buffer, PATH_MAX);
|
|
|
|
if (directory == 0)
|
|
{
|
|
builtin_error ("%s: %s", bash_getcwd_errstr, strerror (errno));
|
|
free (buffer);
|
|
}
|
|
}
|
|
else
|
|
directory = get_working_directory ("pwd");
|
|
|
|
if (directory)
|
|
{
|
|
printf ("%s\n", directory);
|
|
fflush (stdout);
|
|
free (directory);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Do the work of changing to the directory NEWDIR. Handle symbolic
|
|
link following, etc. */
|
|
|
|
static int
|
|
change_to_directory (newdir, nolinks)
|
|
char *newdir;
|
|
int nolinks;
|
|
{
|
|
char *t;
|
|
|
|
if (nolinks == 0)
|
|
{
|
|
int chdir_return = 0;
|
|
char *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);
|
|
}
|
|
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))
|
|
{
|
|
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++)
|
|
;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|