diff options
Diffstat (limited to 'builtins/cd.def')
-rw-r--r-- | builtins/cd.def | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/builtins/cd.def b/builtins/cd.def new file mode 100644 index 0000000..b87c5d9 --- /dev/null +++ b/builtins/cd.def @@ -0,0 +1,685 @@ +This file is cd.def, from which is created cd.c. It implements the +builtins "cd" and "pwd" in Bash. + +Copyright (C) 1987-2022 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 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. + +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> + +#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" +#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> + +#include "../shell.h" +#include "../flags.h" +#include "maxpath.h" +#include "common.h" +#include "bashgetopt.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern const char * const bash_getcwd_errstr; + +static int bindpwd PARAMS((int)); +static int setpwd PARAMS((char *)); +static char *resetpwd PARAMS((char *)); +static int change_to_directory PARAMS((char *, int, int)); + +static int cdxattr PARAMS((char *, char **)); +static void resetxattr PARAMS((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 [-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. If DIR is "-", it is converted to $OLDPWD. + +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, r, canon_failed; + 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"); +#undef tcwd + + /* If canonicalization fails, reset dirname to the_current_working_directory */ + canon_failed = 0; + if (dirname == 0) + { + canon_failed = 1; + dirname = the_current_working_directory; + } + + old_anm = array_needs_making; + pwdvar = get_string_value ("PWD"); + + 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; + } + + if (setpwd (dirname) == EXECUTION_FAILURE) + r = EXECUTION_FAILURE; + if (canon_failed && eflag) + r = EXECUTION_FAILURE; + + if (dirname && dirname != the_current_working_directory) + free (dirname); + + 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 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 (), + 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, e; + +#if defined (RESTRICTED_SHELL) + if (restricted) + { + sh_restricted ((char *)NULL); + return (EXECUTION_FAILURE); + } +#endif /* RESTRICTED_SHELL */ + + eflag = 0; + no_symlinks = no_symbolic_links; + xattrflag = 0; + reset_internal_getopt (); +#if defined (O_XATTR) + while ((opt = internal_getopt (list, "eLP@")) != -1) +#else + while ((opt = internal_getopt (list, "eLP")) != -1) +#endif + { + switch (opt) + { + case 'P': + no_symlinks = 1; + break; + case 'L': + no_symlinks = 0; + break; + case 'e': + eflag = 1; + break; +#if defined (O_XATTR) + case '@': + xattrflag = 1; + break; +#endif + CASE_HELPOPT; + default: + builtin_usage (); + 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) + { + /* `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; + } +#if defined (CD_COMPLAINS) + else if (list->next) + { + builtin_error (_("too many arguments")); + return (EXECUTION_FAILURE); + } +#endif +#if 0 + else if (list->word->word[0] == '\0') + { + builtin_error (_("null directory")); + return (EXECUTION_FAILURE); + } +#endif + 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); + } +#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 (privileged_mode == 0 && (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, 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 && (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] + 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); + } +#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, xattrflag)) + { + 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, xattrflag)) + { + 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 = dirspell (dirname); + if (temp && change_to_directory (temp, no_symlinks, xattrflag)) + { + printf ("%s\n", temp); + free (temp); + return (bindpwd (no_symlinks)); + } + else + FREE (temp); + } + + e = errno; + temp = printable_filename (dirname, 0); + builtin_error ("%s: %s", temp, strerror (e)); + if (temp != dirname) + free (temp); + return (EXECUTION_FAILURE); +} + +$BUILTIN pwd +$FUNCTION pwd_builtin +$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 + 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, pflag; + + verbatim_pwd = no_symbolic_links; + pflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "LP")) != -1) + { + switch (opt) + { + case 'P': + verbatim_pwd = pflag = 1; + break; + case 'L': + verbatim_pwd = 0; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + +#define tcwd the_current_working_directory + + 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); + return (sh_chkwrite (opt)); + } + 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, xattr) + char *newdir; + int nolinks, xattr; +{ + char *t, *tdir, *ndir; + int err, canon_failed, r, ndlen; + + 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); + + ndlen = strlen (newdir); + + /* 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 defined (ENAMETOOLONG) + if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX)) +#else + if (posixly_correct && nolinks == 0 && canon_failed && ndlen > PATH_MAX) +#endif + { +#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 (r == 0) + { + /* 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) + { + free (tdir); + return (0); + } + + err = errno; + + /* 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. + POSIX requires that we just fail here, so we do in posix mode. */ + if (posixly_correct == 0 && chdir (newdir) == 0) + { + t = resetpwd ("cd"); + if (t == 0) + set_working_directory (tdir); + else + free (t); + + r = 1; + } + else + { + errno = err; + r = 0; + } + + free (tdir); + return r; +} |