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

Reply via email to