Configuration Information [Automatically generated, do not change]: Machine: x86_64 OS: freebsd13.2 Compiler: cc Compilation CFLAGS: -g -O2 uname output: FreeBSD localhost 13.2-RELEASE-p1 FreeBSD 13.2-RELEASE-p1 GENERIC amd64 Machine Type: x86_64-unknown-freebsd13.2
Bash Version: 5.2 Patch Level: 15 Release Status: release Description: Setting OLDPWD to anything other than an absolute path causes `cd -` to behave incorrectly, defaulting to ./$OLDPWD instead of being equivalent to `cd $OLDPWD` per POSIX requirements. I have tried the following other shells, none of which have this bug: - ksh93u+m/1.0.6 - oksh (OpenBSD ksh port) - mksh - pdksh - dash - BusyBox (ash) - zsh with `setopt POSIX_CD` - FreeBSD sh (not a POSIX sh, btw) Repeat-By: $ set -e $ mkdir -p /tmp/d1/x/x $ CDPATH=/tmp/d1 $ cd x /tmp/d1/x $ cd x /tmp/d1/x $ echo $PWD $OLDPWD /tmp/d1/x /tmp/d1/x $ OLDPWD=x $ cd - # BUG x $ pwd # BUG result /tmp/d1/x/x $ OLDPWD=x $ cd "$OLDPWD" /tmp/d1/x Fix: Make `cd -` retrieve the value of `$OLDPWD`, then behave as if `cd $OLDPWD` was used with the exception that the new working directory is still printed. Attached is a sample patch that revises the logic to this: if list is null ... #ifdef CD_COMPLAINS else if list->next ... #endif else if list->word->word == "-" dirname = $OLDPWD else return EXECUTION_FAILURE lflag = LCD_PRINTPATH else dirname = list->word->word end if !abspath(dirname) and privileged_mode == 0 and (cdpath = get_string_value("CDPATH")) // if things work with $CDPATH, return early end end // regular cd without using $CDPATH
diff --git a/builtins/cd.def b/builtins/cd.def index b87c5d9d..2482e4c9 100644 --- a/builtins/cd.def +++ b/builtins/cd.def @@ -343,75 +343,78 @@ cd_builtin (list) return (EXECUTION_FAILURE); } #endif - else if (list->word->word[0] == '-' && list->word->word[1] == '\0') + else { - /* This is `cd -', equivalent to `cd $OLDPWD' */ - dirname = get_string_value ("OLDPWD"); - - if (dirname == 0) + if (list->word->word[0] == '-' && list->word->word[1] == '\0') { - builtin_error (_("OLDPWD not set")); - return (EXECUTION_FAILURE); - } + /* 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; + lflag = interactive ? LCD_PRINTPATH : 0; #else - lflag = LCD_PRINTPATH; /* According to SUSv3 */ + 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; + } + else + dirname = list->word->word; - /* Find directory in $CDPATH. */ - path_index = 0; - while (path = extract_colon_unit (cdpath, &path_index)) + if (!absolute_pathname (dirname) && + privileged_mode == 0 && + (cdpath = get_string_value ("CDPATH"))) { - /* 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)) + /* Find directory in $CDPATH. */ + path_index = 0; + while (path = extract_colon_unit (cdpath, &path_index)) { - /* 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); + /* 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)); + /* 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)); + return (bindpwd (no_symlinks)); #endif + } + else + free (temp); } - 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); - } + /* 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. */