This file is cd.def, from which is created cd.c.  It implements the
builtins "cd", "pwd", "pushd", "popd", and "dirs" in Bash.

Copyright (C) 1987, 1989, 1991 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 1, 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; see the file COPYING.  If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

$PRODUCES cd.c

#include <stdio.h>
#include <sys/param.h>

#if defined (HAVE_STRING_H)
#  include <string.h>
#else /* !HAVE_STRING_H */
#  include <strings.h>
#endif /* !HAVE_STRING_H */

#include <errno.h>
#include <tilde/tilde.h>

#include "../shell.h"
#include "../flags.h"
#include "../maxpath.h"
#include "common.h"

#if !defined (errno)
extern int errno;
#endif /* !errno */

static int change_to_directory (), cd_to_string ();

$BUILTIN cd
$FUNCTION cd_builtin
$SHORT_DOC cd [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 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 variable `cdable_vars' exists, then try the word as a variable
name.  If that variable has a value, then cd to the value of that
variable.
$END

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

#if defined (RESTRICTED_SHELL)
  if (restricted)
    {
      builtin_error ("restricted");
      return (EXECUTION_FAILURE);
    }
#endif /* RESTRICTED_SHELL */

  if (list)
    {
      char *extract_colon_unit ();
      char *path_string = get_string_value ("CDPATH");
      char *path;
      int path_index = 0, dirlen, pathlen;

      dirname = list->word->word;

      if (path_string && !absolute_pathname (dirname))
	{
	  while ((path = extract_colon_unit (path_string, &path_index)))
	    {
	      char *dir;

	      if (*path == '~')
		{
		  char *te_string = tilde_expand (path);

		  free (path);
		  path = te_string;
		}

	      if (!*path)
		{
		  free (path);
		  path = xmalloc (2);
		  path[0] = '.';	/* by definition. */
		  path[1] = '\0';
		}

	      dirlen = strlen (dirname);
	      pathlen = strlen (path);
	      dir = xmalloc (2 + dirlen + pathlen);
	      strcpy (dir, path);
	      if (path[pathlen - 1] != '/')
	        {
	          dir[pathlen++] = '/';
	          dir[pathlen] = '\0';
	        }
	      strcpy (dir + pathlen, dirname);
	      free (path);

	      if (change_to_directory (dir))
		{
		  /* replaces (strncmp (dir, "./", 2) != 0) */
		  if (dir[0] != '.' || dir[1] != '/')
		    printf ("%s\n", dir);

		  free (dir);
		  goto bind_and_exit;
		}
	      else
		free (dir);
	    }
	}

      if (!change_to_directory (dirname))
	{
	  /* Maybe this is `cd -', equivalent to `cd $OLDPWD' */
	  if (dirname[0] == '-' && dirname[1] == '\0')
	    {
	      char *t = get_string_value ("OLDPWD");

	      if (t && change_to_directory (t))
		goto bind_and_exit;
	    }

	  /* If the user requests it, then perhaps this is the name of
	     a shell variable, whose value contains the directory to
	     change to.  If that is the case, then change to that
	     directory. */
	  if (find_variable ("cdable_vars"))
	    {
	      char *t = get_string_value (dirname);

	      if (t && change_to_directory (t))
		{
		  printf ("%s\n", t);
		  goto bind_and_exit;
		}
	    }

	  file_error (dirname);
	  return (EXECUTION_FAILURE);
	}
      goto bind_and_exit;
    }
  else
    {
      dirname = get_string_value ("HOME");

      if (!dirname)
	return (EXECUTION_FAILURE);

      if (!change_to_directory (dirname))
	{
	  file_error (dirname);
	  return (EXECUTION_FAILURE);
	}

    bind_and_exit:
      {
	char *directory;

	directory = get_working_directory ("cd");

	bind_variable ("OLDPWD", get_string_value ("PWD"));
	bind_variable ("PWD", directory);

	FREE (directory);
      }
      return (EXECUTION_SUCCESS);
    }
}

$BUILTIN pwd
$FUNCTION pwd_builtin
$SHORT_DOC pwd
Print the current working directory.
$END

/* Non-zero means that pwd always give verbatim directory, regardless of
   symbolic link following. */
static int verbatim_pwd;

/* Print the name of the current working directory. */
pwd_builtin (list)
     WORD_LIST *list;
{
  char *directory, *s;

#if 0
  no_args (list);
#else
  verbatim_pwd = no_symbolic_links;
  if (list && (s = list->word->word) && s[0] == '-' && s[1] == 'P' && !s[2])
    verbatim_pwd = 1;
#endif

  if (verbatim_pwd)
    {
      char *buffer = xmalloc (MAXPATHLEN);
      directory = getwd (buffer);

      if (!directory)
	{
	  builtin_error ("%s", buffer);
	  free (buffer);
	}
    }
  else
    directory = get_working_directory ("pwd");

  if (directory)
    {
      printf ("%s\n", directory);
      fflush (stdout);
      free (directory);
      return (EXECUTION_SUCCESS);
    }
  else
    return (EXECUTION_FAILURE);
}

$BUILTIN pushd
$FUNCTION pushd_builtin
$DEPENDS_ON PUSHD_AND_POPD
$SHORT_DOC pushd [dir | +n | -n]
Adds a directory to the top of the directory stack, or rotates
the stack, making the new top of the stack the current working
directory.  With no arguments, exchanges the top two directories.

+n	Rotates the stack so that the Nth directory (counting
	from the left of the list shown by `dirs') is at the top.

-n	Rotates the stack so that the Nth directory (counting
	from the right) is at the top.

dir	adds DIR to the directory stack at the top, making it the
	new current working directory.

You can see the directory stack with the `dirs' command.
$END

#if defined (PUSHD_AND_POPD)
/* Some useful commands whose behaviour has been observed in Csh. */

/* The list of remembered directories. */
static char **pushd_directory_list = (char **)NULL;

/* Number of existing slots in this list. */
static int directory_list_size = 0;

/* Offset to the end of the list. */
static int directory_list_offset = 0;

pushd_builtin (list)
     WORD_LIST *list;
{
  char *temp, *current_directory;
  int j = directory_list_offset - 1;
  char direction = '+';

  /* If there is no argument list then switch current and
     top of list. */
  if (!list)
    {
      if (!directory_list_offset)
	{
	  builtin_error ("No other directory");
	  return (EXECUTION_FAILURE);
	}

      current_directory = get_working_directory ("pushd");
      if (!current_directory)
	return (EXECUTION_FAILURE);

      temp = pushd_directory_list[j];
      pushd_directory_list[j] = current_directory;
      goto change_to_temp;
    }
  else
    {
      direction = *(list->word->word);
      if (direction == '+' || direction == '-')
	{
	  int num;
	  if (1 == sscanf (&(list->word->word)[1], "%d", &num))
	    {
	      if (direction == '-')
		num = directory_list_offset - num;

	      if (num > directory_list_offset || num < 0)
		{
		  if (!directory_list_offset)
		    builtin_error ("Directory stack empty");
		  else
		    builtin_error ("Stack contains only %d directories",
				    directory_list_offset + 1);
		  return (EXECUTION_FAILURE);
		}
	      else
		{
		  /* Rotate the stack num times.  Remember, the
		     current directory acts like it is part of the
		     stack. */
		  temp = get_working_directory ("pushd");

		  if (!num)
		    goto change_to_temp;

		  do
		    {
		      char *top =
			pushd_directory_list[directory_list_offset - 1];

		      for (j = directory_list_offset - 2; j > -1; j--)
			pushd_directory_list[j + 1] = pushd_directory_list[j];

		      pushd_directory_list[j + 1] = temp;

		      temp = top;
		      num--;
		    }
		  while (num);

		  temp = savestring (temp);
		change_to_temp:
		  {
		    int tt = EXECUTION_FAILURE;

		    if (temp)
		      {
			tt = cd_to_string (temp);
			free (temp);
		      }

		    if ((tt == EXECUTION_SUCCESS))
		      dirs_builtin ((WORD_LIST *)NULL);

		    return (tt);
		  }
		}
	    }
	}

      /* Change to the directory in list->word->word.  Save the current
	 directory on the top of the stack. */
      current_directory = get_working_directory ("pushd");
      if (!current_directory)
	return (EXECUTION_FAILURE);

      if (cd_builtin (list) == EXECUTION_SUCCESS)
	{
	  if (directory_list_offset == directory_list_size)
	    {
	      pushd_directory_list = (char **)
		xrealloc (pushd_directory_list,
			  (directory_list_size += 10) * sizeof (char *));
	    }
	  pushd_directory_list[directory_list_offset++] = current_directory;

	  dirs_builtin ((WORD_LIST *)NULL);

	  return (EXECUTION_SUCCESS);
	}
      else
	{
	  free (current_directory);
	  return (EXECUTION_FAILURE);
	}
    }
}
#endif /* PUSHD_AND_POPD */

$BUILTIN dirs
$FUNCTION dirs_builtin
$DEPENDS_ON PUSHD_AND_POPD
$SHORT_DOC dirs [-l]
Display the list of currently remembered directories.  Directories
find their way onto the list with the `pushd' command; you can get
back up through the list with the `popd' command.

The -l flag specifies that `dirs' should not print shorthand versions
of directories which are relative to your home directory.  This means
that `~/bin' might be displayed as `/homes/bfox/bin'.
$END

#if defined (PUSHD_AND_POPD)
/* Print the current list of directories on the directory stack. */
dirs_builtin (list)
     WORD_LIST *list;
{
  int i, format, desired_index, index_flag;
  char *temp, *w = NULL;

  format = index_flag = 0;
  desired_index = -1;
  /* Maybe do long form or print specific dir stack entry? */
  while (list)
    {
      if (strcmp (list->word->word, "-l") == 0)
	{
	  format++;
	  list = list->next;
	}
      else if (*list->word->word == '+' && all_digits (list->word->word + 1))
	{
	  w = list->word->word + 1;
	  index_flag = 1;
	  i = atoi (w);
	  /* dirs +0 prints the current working directory. */
	  if (i == 0)
	    desired_index = i;
	  else if (i == directory_list_offset)
	    {
	      desired_index = 0;
	      index_flag = 2;
	    }
	  else
	    desired_index = directory_list_offset - i;
	  list = list->next;
	}
      else if (*list->word->word == '-' && all_digits (list->word->word + 1))
	{
	  w = list->word->word + 1;
	  i = atoi (w);
	  index_flag = 2;
	  /* dirs -X where X is directory_list_offset prints the current
	     working directory. */
	  if (i == directory_list_offset)
	    {
	      index_flag = 1;
	      desired_index = 0;
	    }
	  else
	    desired_index = i;
	  list = list->next;
	}
      else
	{
	  bad_option (list->word->word);
	  return (EXECUTION_FAILURE);
	}
    }

  if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
    {
      if (directory_list_offset == 0)
	builtin_error ("directory stack empty");
      else
	builtin_error ("%s: bad directory stack index", w);
      return (EXECUTION_FAILURE);
    }

  /* The first directory printed is always the current working directory. */
  if (!index_flag || (index_flag == 1 && desired_index == 0))
    {
      temp = get_working_directory ("dirs");
      if (!temp)
	temp = savestring ("<no directory>");
      printf ("%s", format ? temp : polite_directory_format (temp));
      free (temp);
      if (index_flag)
	{
	  putchar ('\n');
	  return EXECUTION_SUCCESS;
	}
    }

#define DIRSTACK_ENTRY(i) \
	format ? pushd_directory_list[i] \
	       : polite_directory_format (pushd_directory_list[i])

  /* Now print the requested directory stack entries. */
  if (index_flag)
    printf ("%s", DIRSTACK_ENTRY (desired_index));
  else
    for (i = (directory_list_offset - 1); i > -1; i--)
      printf (" %s", DIRSTACK_ENTRY (i));

  putchar ('\n');
  fflush (stdout);
  return (EXECUTION_SUCCESS);
}
#endif /* PUSHD_AND_POPD */

$BUILTIN popd
$FUNCTION popd_builtin
$DEPENDS_ON PUSHD_AND_POPD
$SHORT_DOC popd [+n | -n]
Removes entries from the directory stack.  With no arguments,
removes the top directory from the stack, and cd's to the new
top directory.

+n	removes the Nth entry counting from the left of the list
	shown by `dirs', starting with zero.  For example: `popd +0'
	removes the first directory, `popd +1' the second.

-n	removes the Nth entry counting from the right of the list
	shown by `dirs', starting with zero.  For example: `popd -0'
	removes the last directory, `popd -1' the next to last.

You can see the directory stack with the `dirs' command.
$END

#if defined (PUSHD_AND_POPD)
/* Pop the directory stack, and then change to the new top of the stack.
   If LIST is non-null it should consist of a word +N or -N, which says
   what element to delete from the stack.  The default is the top one. */
popd_builtin (list)
     WORD_LIST *list;
{
  register int i;
  int which = 0;
  char direction = '+';

  if (list)
    {
      direction = *(list->word->word);

      if ((direction != '+' && direction != '-') ||
	  (1 != sscanf (&((list->word->word)[1]), "%d", &which)))
	{
	  builtin_error ("bad arg `%s'", list->word->word);
	  return (EXECUTION_FAILURE);
	}
    }

  if (which > directory_list_offset || (!directory_list_offset && !which))
    {
      if (!directory_list_offset)
	builtin_error ("Directory stack empty");
      else
	builtin_error ("Stack contains only %d directories",
			directory_list_offset + 1);
      return (EXECUTION_FAILURE);
    }

  /* Handle case of no specification, or top of stack specification. */
  if ((direction == '+' && which == 0) ||
      (direction == '-' && which == directory_list_offset))
    {
      i = cd_to_string (pushd_directory_list[directory_list_offset - 1]);
      if (i != EXECUTION_SUCCESS)
	return (i);
      free (pushd_directory_list[--directory_list_offset]);
    }
  else
    {
      /* Since an offset other than the top directory was specified,
	 remove that directory from the list and shift the remainder
	 of the list into place. */

      if (direction == '+')
	i = directory_list_offset - which;
      else
	i = which;

      free (pushd_directory_list[i]);
      directory_list_offset--;

      /* Shift the remainder of the list into place. */
      for (; i < directory_list_offset; i++)
	pushd_directory_list[i] = pushd_directory_list[i + 1];
    }

  dirs_builtin ((WORD_LIST *)NULL);

  return (EXECUTION_SUCCESS);
}
#endif /* PUSHD_AND_POPD */

/* Do the work of changing to the directory NEWDIR.  Handle symbolic
   link following, etc. */

static int
change_to_directory (newdir)
     char *newdir;
{
  char *t;

  if (!no_symbolic_links)
    {
      int chdir_return = 0;
      char *tdir = (char *)NULL;

      if (!the_current_working_directory)
	{
	  t = get_working_directory ("cd_links");
	  FREE (t);
	}

      if (the_current_working_directory)
	t = make_absolute (newdir, the_current_working_directory);
      else
	t = savestring (newdir);

      /* TDIR is the canonicalized absolute pathname of the NEWDIR. */
      tdir = canonicalize_pathname (t);

      /* Use the canonicalized version of NEWDIR, or, if canonicalization
	 failed, use the non-canonical form. */
      if (tdir && *tdir)
	free (t);
      else
	{
	  FREE (tdir);

	  tdir = t;
	}

      if (chdir (tdir) < 0)
	{
	  int err;

	  chdir_return = 0;
	  free (tdir);

	  err = errno;

	  /* We failed changing to the canonicalized directory name.  Try
	     what the user passed verbatim.  If we succeed, reinitialize
	     the_current_working_directory. */
	  if (chdir (newdir) == 0)
	    {
	      chdir_return = 1;
	      if (the_current_working_directory)
		{
		  free (the_current_working_directory);
		  the_current_working_directory = (char *)NULL;
		}

	      tdir = get_working_directory ("cd");
	      FREE (tdir);
	    }
	  else
	    errno = err;
	}
      else
	{
	  chdir_return = 1;

	  FREE (the_current_working_directory);
	  the_current_working_directory = tdir;
	}

      return (chdir_return);
    }
  else
    {
      if (chdir (newdir) < 0)
	return (0);
      else
	return (1);
    }
}

/* Switch to the directory in NAME.  This uses the cd_builtin to do the work,
   so if the result is EXECUTION_FAILURE then an error message has already
   been printed. */
static int
cd_to_string (name)
     char *name;
{
  WORD_LIST *tlist = make_word_list (make_word (name), NULL);
  int result = (cd_builtin (tlist));
  dispose_words (tlist);
  return (result);
}
