439 lines
11 KiB
Modula-2
439 lines
11 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 2, 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, 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
|
|
|
$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"
|
|
#ifndef _MINIX
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#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 ();
|
|
|
|
/* 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
|
|
|
|
static int
|
|
bindpwd (no_symlinks)
|
|
int no_symlinks;
|
|
{
|
|
char *dirname, *pwdvar;
|
|
int old_symlinks, old_anm;
|
|
SHELL_VAR *tvar;
|
|
|
|
#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))
|
|
{
|
|
update_export_env_inplace ("OLDPWD=", 7, pwdvar);
|
|
array_needs_making = 0;
|
|
}
|
|
|
|
tvar = bind_variable ("PWD", dirname);
|
|
if (old_anm == 0 && array_needs_making && exported_p (tvar))
|
|
{
|
|
update_export_env_inplace ("PWD=", 4, dirname);
|
|
array_needs_making = 0;
|
|
}
|
|
|
|
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
|
|
restrictions properly. */
|
|
int
|
|
cd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *dirname, *cdpath, *path, *temp;
|
|
int path_index, no_symlinks, opt, lflag;
|
|
|
|
#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;
|
|
|
|
lflag = (cdable_vars ? LCD_DOVARS : 0) |
|
|
((interactive && cdspelling) ? LCD_DOSPELL : 0);
|
|
|
|
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);
|
|
}
|
|
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)
|
|
{
|
|
builtin_error ("OLDPWD not set");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
lflag = interactive ? LCD_PRINTPATH : 0;
|
|
}
|
|
else if (absolute_pathname (list->word->word))
|
|
dirname = list->word->word;
|
|
else if (cdpath = get_string_value ("CDPATH"))
|
|
{
|
|
dirname = list->word->word;
|
|
|
|
/* 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))
|
|
{
|
|
/* 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);
|
|
}
|
|
|
|
/* 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])
|
|
{
|
|
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, 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))
|
|
{
|
|
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 (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);
|
|
}
|
|
|
|
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.
|
|
$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;
|
|
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;
|
|
|
|
#define tcwd the_current_working_directory
|
|
|
|
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);
|
|
if (ferror (stdout))
|
|
{
|
|
builtin_error ("write error: %s", strerror (errno));
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Do the work of changing to the directory NEWDIR. Handle symbolic
|
|
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, *tdir;
|
|
int err;
|
|
|
|
tdir = (char *)NULL;
|
|
|
|
if (the_current_working_directory == 0)
|
|
{
|
|
t = get_working_directory ("chdir");
|
|
FREE (t);
|
|
}
|
|
|
|
t = make_absolute (newdir, the_current_working_directory);
|
|
|
|
/* 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 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);
|
|
}
|
|
|
|
/* We failed to change to the appropriate directory name. If we tried
|
|
what the user passed (nolinks != 0), punt now. */
|
|
if (nolinks)
|
|
return (0);
|
|
|
|
err = errno;
|
|
free (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 (1);
|
|
}
|
|
else
|
|
{
|
|
errno = err;
|
|
return (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;
|
|
}
|
|
}
|