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. */