i-bash/builtins/cd.def
2009-09-12 16:46:50 +00:00

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;
}