675 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Modula-2
		
	
	
	
	
	
			
		
		
	
	
			675 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Modula-2
		
	
	
	
	
	
This file is cd.def, from which is created cd.c.  It implements the
 | 
						|
builtins "cd" and "pwd" in Bash.
 | 
						|
 | 
						|
Copyright (C) 1987-2016 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 int posixly_correct;
 | 
						|
extern int array_needs_making;
 | 
						|
extern const char * const bash_getcwd_errstr;
 | 
						|
 | 
						|
static int bindpwd __P((int));
 | 
						|
static int setpwd __P((char *));
 | 
						|
static char *resetpwd __P((char *));
 | 
						|
static int change_to_directory __P((char *, int, int));
 | 
						|
 | 
						|
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 [-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, 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");
 | 
						|
#undef tcwd
 | 
						|
 | 
						|
  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 (dirname == 0 && 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, dlen;
 | 
						|
 | 
						|
  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);
 | 
						|
  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 (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. */
 | 
						|
  if (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;
 | 
						|
}
 |