Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / builtins / cd.def
index 77e8f82..f66e68c 100644 (file)
@@ -1,23 +1,22 @@
 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>
@@ -32,13 +31,15 @@ $PRODUCES cd.c
 #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>
@@ -55,42 +56,94 @@ extern int errno;
 
 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");
@@ -99,29 +152,105 @@ bindpwd (no_symlinks)
   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 (),
@@ -137,14 +266,20 @@ cd_builtin (list)
 #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)
        {
@@ -154,15 +289,25 @@ cd_builtin (list)
        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)
     {
@@ -171,11 +316,18 @@ cd_builtin (list)
 
       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' */
@@ -183,14 +335,18 @@ cd_builtin (list)
 
       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;
 
@@ -203,24 +359,30 @@ cd_builtin (list)
          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]
@@ -231,13 +393,14 @@ cd_builtin (list)
          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);
@@ -250,7 +413,7 @@ cd_builtin (list)
   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));
@@ -262,10 +425,11 @@ cd_builtin (list)
      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
@@ -278,10 +442,19 @@ cd_builtin (list)
 
 $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
@@ -294,23 +467,24 @@ pwd_builtin (list)
      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;
@@ -319,20 +493,29 @@ pwd_builtin (list)
 
   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);
@@ -345,12 +528,12 @@ pwd_builtin (list)
    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;
 
@@ -368,73 +551,111 @@ change_to_directory (newdir, nolinks)
   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;
 }