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.
+Copyright (C) 1987-2013 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 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 3 of the License, 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.
+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.
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
$PRODUCES cd.c
#include <config.h>
#include "../bashtypes.h"
#include "posixdir.h"
#include "posixstat.h"
-#ifndef _MINIX
+#if defined (HAVE_SYS_PARAM_H)
#include <sys/param.h>
#endif
+#include <fcntl.h>
#include <stdio.h>
#include "../bashansi.h"
+#include "../bashintl.h"
#include <errno.h>
#include <tilde/tilde.h>
extern int posixly_correct;
extern int array_needs_making;
-extern char *bash_getcwd_errstr;
+extern const char * const bash_getcwd_errstr;
static int bindpwd __P((int));
-static int change_to_directory __P((char *, int));
+static int setpwd __P((char *));
+static char *resetpwd __P((char *));
+static int change_to_directory __P((char *, int, int));
-static char *cdspell __P((char *));
+static int cdxattr __P((char *, char **));
+static void resetxattr __P((void));
/* Change this to 1 to get cd spelling correction by default. */
int cdspelling = 0;
int cdable_vars;
+static int eflag; /* file scope so bindpwd() can see it */
+static int xattrflag; /* O_XATTR support for openat */
+static int xattrfd = -1;
+
$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.
+$SHORT_DOC cd [-L|[-P [-e]] [-@]] [dir]
+Change the shell working directory.
+
+Change the current directory to DIR. The default DIR is the value of the
+HOME shell variable.
+
+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. 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,
+the word is assumed to be a variable name. If that variable has a value,
+its value is used for DIR.
+
+Options:
+ -L force symbolic links to be followed: resolve symbolic links in
+ DIR after processing instances of `..'
+ -P use the physical directory structure without following symbolic
+ links: resolve symbolic links in DIR before processing instances
+ of `..'
+ -e if the -P option is supplied, and the current working directory
+ cannot be determined successfully, exit with a non-zero status
+#if defined (O_XATTR)
+ -@ on systems that support it, present a file with extended attributes
+ as a directory containing the file attributes
+#endif
+
+The default is to follow symbolic links, as if `-L' were specified.
+`..' is processed by removing the immediately previous pathname component
+back to a slash or the beginning of DIR.
+
+Exit Status:
+Returns 0 if the directory is changed, and if $PWD is set successfully when
+-P is used; non-zero otherwise.
$END
+/* Just set $PWD, don't change OLDPWD. Used by `pwd -P' in posix mode. */
+static int
+setpwd (dirname)
+ char *dirname;
+{
+ int old_anm;
+ SHELL_VAR *tvar;
+
+ old_anm = array_needs_making;
+ tvar = bind_variable ("PWD", dirname ? dirname : "", 0);
+ if (tvar && readonly_p (tvar))
+ return EXECUTION_FAILURE;
+ if (tvar && old_anm == 0 && array_needs_making && exported_p (tvar))
+ {
+ update_export_env_inplace ("PWD=", 4, dirname ? dirname : "");
+ array_needs_making = 0;
+ }
+ return EXECUTION_SUCCESS;
+}
+
static int
bindpwd (no_symlinks)
int no_symlinks;
{
char *dirname, *pwdvar;
- int old_anm;
+ int old_anm, r;
SHELL_VAR *tvar;
+ r = sh_chkwrite (EXECUTION_SUCCESS);
+
#define tcwd the_current_working_directory
dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd)
: get_working_directory ("cd");
old_anm = array_needs_making;
pwdvar = get_string_value ("PWD");
- tvar = bind_variable ("OLDPWD", pwdvar);
+ tvar = bind_variable ("OLDPWD", pwdvar, 0);
+ if (tvar && readonly_p (tvar))
+ r = EXECUTION_FAILURE;
+
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 (setpwd (dirname) == EXECUTION_FAILURE)
+ r = EXECUTION_FAILURE;
+ if (dirname == 0 && eflag)
+ r = EXECUTION_FAILURE;
if (dirname && dirname != the_current_working_directory)
free (dirname);
- return (EXECUTION_SUCCESS);
+
+ return (r);
+}
+
+/* Call get_working_directory to reset the value of
+ the_current_working_directory () */
+static char *
+resetpwd (caller)
+ char *caller;
+{
+ char *tdir;
+
+ FREE (the_current_working_directory);
+ the_current_working_directory = (char *)NULL;
+ tdir = get_working_directory (caller);
+ return (tdir);
+}
+
+static int
+cdxattr (dir, ndirp)
+ char *dir; /* don't assume we can always free DIR */
+ char **ndirp; /* return new constructed directory name */
+{
+#if defined (O_XATTR)
+ int apfd, fd, r, e;
+ char buf[11+40+40]; /* construct new `fake' path for pwd */
+
+ apfd = openat (AT_FDCWD, dir, O_RDONLY|O_NONBLOCK);
+ if (apfd < 0)
+ return -1;
+ fd = openat (apfd, ".", O_XATTR);
+ e = errno;
+ close (apfd); /* ignore close error for now */
+ errno = e;
+ if (fd < 0)
+ return -1;
+ r = fchdir (fd); /* assume fchdir exists everywhere with O_XATTR */
+ if (r < 0)
+ {
+ close (fd);
+ return -1;
+ }
+ /* NFSv4 and ZFS extended attribute directories do not have names which are
+ visible in the standard Unix directory tree structure. To ensure we have
+ a valid name for $PWD, we synthesize one under /proc, but to keep that
+ path valid, we need to keep the file descriptor open as long as we are in
+ this directory. This imposes a certain structure on /proc. */
+ if (ndirp)
+ {
+ sprintf (buf, "/proc/%d/fd/%d", getpid(), fd);
+ *ndirp = savestring (buf);
+ }
+
+ if (xattrfd >= 0)
+ close (xattrfd);
+ xattrfd = fd;
+
+ return r;
+#else
+ return -1;
+#endif
+}
+
+/* Clean up the O_XATTR baggage. Currently only closes xattrfd */
+static void
+resetxattr ()
+{
+#if defined (O_XATTR)
+ if (xattrfd >= 0)
+ {
+ close (xattrfd);
+ xattrfd = -1;
+ }
+#else
+ xattrfd = -1; /* not strictly necessary */
+#endif
}
#define LCD_DOVARS 0x001
#define LCD_DOSPELL 0x002
#define LCD_PRINTPATH 0x004
-#define LCD_FREEDIRNAME 0x010
+#define LCD_FREEDIRNAME 0x008
/* This builtin is ultimately the way that all user-visible commands should
change the current working directory. It is called by cd_to_string (),
#if defined (RESTRICTED_SHELL)
if (restricted)
{
- builtin_error ("restricted");
+ sh_restricted ((char *)NULL);
return (EXECUTION_FAILURE);
}
#endif /* RESTRICTED_SHELL */
+ eflag = 0;
no_symlinks = no_symbolic_links;
+ xattrflag = 0;
reset_internal_getopt ();
- while ((opt = internal_getopt (list, "LP")) != -1)
+#if defined (O_XATTR)
+ while ((opt = internal_getopt (list, "eLP@")) != -1)
+#else
+ while ((opt = internal_getopt (list, "eLP")) != -1)
+#endif
{
switch (opt)
{
case 'L':
no_symlinks = 0;
break;
+ case 'e':
+ eflag = 1;
+ break;
+#if defined (O_XATTR)
+ case '@':
+ xattrflag = 1;
+ break;
+#endif
default:
builtin_usage ();
- return (EXECUTION_FAILURE);
+ return (EX_USAGE);
}
}
list = loptend;
lflag = (cdable_vars ? LCD_DOVARS : 0) |
((interactive && cdspelling) ? LCD_DOSPELL : 0);
+ if (eflag && no_symlinks == 0)
+ eflag = 0;
if (list == 0)
{
if (dirname == 0)
{
- builtin_error ("HOME not set");
+ builtin_error (_("HOME not set"));
return (EXECUTION_FAILURE);
}
lflag = 0;
}
+#if defined (CD_COMPLAINS)
+ else if (list->next)
+ {
+ builtin_error (_("too many arguments"));
+ return (EXECUTION_FAILURE);
+ }
+#endif
else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
{
/* This is `cd -', equivalent to `cd $OLDPWD' */
if (dirname == 0)
{
- builtin_error ("OLDPWD not set");
+ builtin_error (_("OLDPWD not set"));
return (EXECUTION_FAILURE);
}
+#if 0
lflag = interactive ? LCD_PRINTPATH : 0;
+#else
+ lflag = LCD_PRINTPATH; /* According to SUSv3 */
+#endif
}
else if (absolute_pathname (list->word->word))
dirname = list->word->word;
- else if (cdpath = get_string_value ("CDPATH"))
+ else if (privileged_mode == 0 && (cdpath = get_string_value ("CDPATH")))
{
dirname = list->word->word;
temp = sh_makepath (path, dirname, MP_DOTILDE);
free (path);
- if (change_to_directory (temp, no_symlinks))
+ if (change_to_directory (temp, no_symlinks, xattrflag))
{
/* 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);
+ if (opt && (path = no_symlinks ? temp : the_current_working_directory))
+ printf ("%s\n", path);
free (temp);
+#if 0
/* Posix.2 says that after using CDPATH, the resultant
value of $PWD will not contain `.' or `..'. */
return (bindpwd (posixly_correct || no_symlinks));
+#else
+ return (bindpwd (no_symlinks));
+#endif
}
else
free (temp);
}
+#if 0
+ /* changed for bash-4.2 Posix cd description steps 5-6 */
/* 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]
builtin_error ("%s: %s", dirname, strerror (ENOENT));
return (EXECUTION_FAILURE);
}
+#endif
}
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 (change_to_directory (dirname, no_symlinks, xattrflag))
{
if (lflag & LCD_PRINTPATH)
printf ("%s\n", dirname);
if (lflag & LCD_DOVARS)
{
temp = get_string_value (dirname);
- if (temp && change_to_directory (temp, no_symlinks))
+ if (temp && change_to_directory (temp, no_symlinks, xattrflag))
{
printf ("%s\n", temp);
return (bindpwd (no_symlinks));
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))
+ temp = dirspell (dirname);
+ if (temp && change_to_directory (temp, no_symlinks, xattrflag))
{
printf ("%s\n", temp);
+ free (temp);
return (bindpwd (no_symlinks));
}
else
$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.
+$SHORT_DOC pwd [-LP]
+Print the name of the current working directory.
+
+Options:
+ -L print the value of $PWD if it names the current working
+ directory
+ -P print the physical directory, without any symbolic links
+
+By default, `pwd' behaves as if `-L' were specified.
+
+Exit Status:
+Returns 0 unless an invalid option is given or the current directory
+cannot be read.
$END
/* Non-zero means that pwd always prints the physical directory, without
WORD_LIST *list;
{
char *directory;
- int opt;
+ int opt, pflag;
verbatim_pwd = no_symbolic_links;
+ pflag = 0;
reset_internal_getopt ();
while ((opt = internal_getopt (list, "LP")) != -1)
{
switch (opt)
{
case 'P':
- verbatim_pwd = 1;
+ verbatim_pwd = pflag = 1;
break;
case 'L':
verbatim_pwd = 0;
break;
default:
builtin_usage ();
- return (EXECUTION_FAILURE);
+ return (EX_USAGE);
}
}
list = loptend;
directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd)
: get_working_directory ("pwd");
+
+ /* Try again using getcwd() if canonicalization fails (for instance, if
+ the file system has changed state underneath bash). */
+ if ((tcwd && directory == 0) ||
+ (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0))
+ {
+ if (directory && directory != tcwd)
+ free (directory);
+ directory = resetpwd ("pwd");
+ }
+
#undef tcwd
if (directory)
{
+ opt = EXECUTION_SUCCESS;
printf ("%s\n", directory);
+ /* This is dumb but posix-mandated. */
+ if (posixly_correct && pflag)
+ opt = setpwd (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);
+ return (sh_chkwrite (opt));
}
else
return (EXECUTION_FAILURE);
to the working directory. Return 1 on success, 0 on failure. */
static int
-change_to_directory (newdir, nolinks)
+change_to_directory (newdir, nolinks, xattr)
char *newdir;
- int nolinks;
+ int nolinks, xattr;
{
- char *t, *tdir;
- int err;
+ char *t, *tdir, *ndir;
+ int err, canon_failed, r, ndlen, dlen;
tdir = (char *)NULL;
tdir = nolinks ? sh_physpath (t, 0)
: sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+ ndlen = strlen (newdir);
+ dlen = strlen (t);
+
/* Use the canonicalized version of NEWDIR, or, if canonicalization
failed, use the non-canonical form. */
+ canon_failed = 0;
if (tdir && *tdir)
free (t);
else
{
FREE (tdir);
tdir = t;
+ canon_failed = 1;
+ }
+
+ /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath
+ returns NULL (because it checks the path, it will return NULL if the
+ resolved path doesn't exist), fail immediately. */
+ if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX))
+ {
+#if defined ENAMETOOLONG
+ if (errno != ENOENT && errno != ENAMETOOLONG)
+#else
+ if (errno != ENOENT)
+#endif
+ errno = ENOTDIR;
+ free (tdir);
+ return (0);
+ }
+
+#if defined (O_XATTR)
+ if (xattrflag)
+ {
+ r = cdxattr (nolinks ? newdir : tdir, &ndir);
+ if (r >= 0)
+ {
+ canon_failed = 0;
+ free (tdir);
+ tdir = ndir;
+ }
+ else
+ {
+ err = errno;
+ free (tdir);
+ errno = err;
+ return (0); /* no xattr */
+ }
+ }
+ else
+#endif
+ {
+ r = chdir (nolinks ? newdir : tdir);
+ if (r >= 0)
+ resetxattr ();
}
/* If the chdir succeeds, update the_current_working_directory. */
- if (chdir (nolinks ? newdir : tdir) == 0)
+ if (r == 0)
{
- FREE (the_current_working_directory);
- the_current_working_directory = tdir;
-
+ /* If canonicalization failed, but the chdir succeeded, reset the
+ shell's idea of the_current_working_directory. */
+ if (canon_failed)
+ {
+ t = resetpwd ("cd");
+ if (t == 0)
+ set_working_directory (tdir);
+ else
+ free (t);
+ }
+ else
+ set_working_directory (tdir);
+
+ free (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);
+ {
+ free (tdir);
+ 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);
+ t = resetpwd ("cd");
+ if (t == 0)
+ set_working_directory (tdir);
+ else
+ free (t);
- return (1);
+ r = 1;
}
else
{
errno = err;
- return (0);
+ r = 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 = (char *)xmalloc (n);
- switch (spname (dirname, guess))
- {
- case -1:
- default:
- free (guess);
- return (char *)NULL;
- case 0:
- case 1:
- return guess;
- }
+ free (tdir);
+ return r;
}