DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T b

⟦05ec3bd44⟧ TextFile

    Length: 84846 (0x14b6e)
    Types: TextFile
    Names: »builtins.c«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦ca1f037a2⟧ »./bash-1.04.tar.Z« 
        └─⟦46465a4db⟧ 
            └─⟦this⟧ »bash-1.04/builtins.c« 

TextFile

/* builtins.c -- the built in shell commands. */

/* Copyright (C) 1987,1989 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. */

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

#ifndef SYSV
#include <sys/time.h>
#include <sys/resource.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <errno.h>
#include "shell.h"
#include "builtins.h"
#include "trap.h"
#include "flags.h"
#include <readline/history.h>

#ifdef JOB_CONTROL
#include "jobs.h"
#endif

extern int errno;		/* Not always in <errno.h>.  Bogusness. */

#ifndef sigmask
#define sigmask(x) (1 << ((x)-1))
#endif

#ifdef SYSV
#include <fcntl.h>
#include <sys/times.h>
#endif

/* Yecch!  Who cares about this gross concept in the first place? */
#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

/* The command name of the currently running function. */
extern char *this_command_name;

/* The list of shell builtins.  Each element is name, function, enabled-p,
   short-doc, long-doc.  The long-doc field should contain a set of indented
   lines.  The function takes a WORD_LIST *, where the first word in the list
   is the first arg to the command.  The list has already been word expanded.

   Procedures which need to look at every simple command (like enable_builtin),
   should tree-walk looking for (array[i].function == (Function *)NULL).  The
   list of executable builtins (in the shell sense) ends there.  Then comes
   the control structure commands, like `if' and `while'.  */

struct builtin shell_builtins[] = {

  { ":", colon_builtin, 1, ":",
      "	No effect; the command does nothing.  A zero exit code is returned" },

  { ".", period_builtin, 1, ". [filename]",
      "	Read and execute commands from FILENAME and return.\n\
	Pathnames in $PATH are used to find the directory containing FILENAME" },

  { "alias", alias_builtin, 1, "alias [ name[=value] ...]",
      "	Alias with no arguments prints the list of aliases in the form\n\
	name=value on standard output.  An alias is defined for each NAME\n\
	whose VALUE is given.  A trailing space in VALUE causes the next\n\
	word to be checked for alias substitution.  Alias returns true\n\
	unless a NAME is given for which no alias has been defined" },

#ifdef JOB_CONTROL
  { "bg", bg_builtin, 1, "bg [job_spec]",
      "	Place JOB_SPEC in the background, as if it had been started with\n\
	`&'.  If JOB_SPEC is not present, the shell's notion of the current\n\
	job is used" },
#endif

  { "break", break_builtin, 1, "break [n]",
      "	Exit from within a FOR, WHILE or UNTIL loop.  If N is specified,\n\
	break N levels" },

  { "builtin", builtin_builtin, 1, "builtin [shell-builtin [args...]]",
      "	Run a shell builtin.  This is useful when you wish to rename a\n\
	shell builtin to be a function, but need the functionality of the\n\
	builtin within the function itself" },

  { "bye", exit_builtin, 1, "bye [n]",
      "	Synonym for exit" },

  { "cd", cd_builtin, 1, "cd [dir]",
      "	Change the current directory to DIR.  The variable $HOME is the\n\
	default DIR.  The variable $CDPATH defines the search path for\n\
	the directory containing DIR.  Alternative directory names are\n\
	separated by a colon (:).  A null directory name is the same as\n\
	the current directory, i.e. `.'.  If DIR begins with a slash (/),\n\
	then $CDPATH is not used" },

  { "continue", continue_builtin, 1, "continue [n]",
      "	Resume the next iteration of the enclosing FOR, WHILE or UNTIL loop.\n\
	If N is specified, resume at the N-th enclosing loop" },

  { "declare", declare_builtin, 1, "declare [-[frx]] name[=value]",
      "	Declare variables and/or give them attributes.  If no NAMEs are\n\
	are given, then display the values of variables instead.  `-f'\n\
	says to use function names only.  `-r' says to make NAMEs readonly.\n\
	`-x' says to make NAMEs export.  Using `+' instead of `-' turns off\n\
	the attribute instead.  When used in a function, makes NAMEs local,\n\
	as with the `local' command" },

#ifdef PUSHD_AND_POPD
  { "dirs", dirs_builtin, 1, "dirs",
      "	Display the list of currently remembered directories.  Directories\n\
	find their way onto the list with the `pushd' command; you can get\n\
	back up through the list with the `popd' command" },
#endif

#ifndef V9_ECHO
  { "echo", echo_builtin, 1, "echo [-n] [arg ...]",
      " Output the ARGs.  If -n is specified, then suppress trailing\n\
        newline" },
#else
  { "echo", echo_builtin, 1, "echo [-n] [-e] [arg ...]",
      "	Output the ARGs.  if -n is specified, the trailing newline is\n\
	suppressed.  If the -e option is given, interpretation of the\n\
	following backslash-escaped characters is turned on:\n\
		\\b     backspace\n\
		\\c     suppress trailing newline\n\
		\\f     form feed\n\
		\\n     new line\n\
		\\r     carriage return\n\
		\\t     horizontal tab\n\
		\\v     vertical tab\n\
		\\num   the character whose ASCII code is NUM (octal)"},
#endif

  { "enable", enable_builtin, 1, "enable [-n name ...]",
      "	Enable and disable builtin shell commands.  This allows\n\
	you to use a disk command which has the same name as a shell\n\
	builtin.  If -n is used, the NAMEs become disabled.  Otherwise\n\
	NAMEs are enabled.  For example, to use the `test' found on your\n\
	path instead of the shell builtin version, you type `enable -n test'" },

  { "eval", eval_builtin, 1, "eval [arg ...]",
      "	Read ARGs as input to the shell and execute the resulting command(s)" },

  { "exec", exec_builtin, 1, "exec [ [-] file [redirections]]",
      "	Exec FILE, replacing this shell with the specified program.\n\
	If FILE is not specified, the redirections take effect in this\n\
	shell.  If the first argument is `-', then place a dash in the\n\
	zeroith arg passed to FILE.  This is what login does.  If the file\n\
	cannot be exec'ed for some reason, the shell exits, unless the\n\
	shell variable \"no_exit_on_failed_exec\" exists" },

  { "exit", exit_builtin, 1, "exit [n]",
      "	Exit the shell with a status of N.  If N is omitted, the exit status\n\
	is that of the last command executed" },

  { "export", export_builtin, 1, "export [-n] [name ...]",
      "	NAMEs are marked for automatic export to the environment of\n\
	subsequently executed commands.  If no NAMEs are given, a list\n\
	of all names that are exported in this shell is printed.  Note\n\
	that function names cannot be exported.  An argument of `-n' says\n\
	to remove the export property from subsequent NAMEs" },

#ifdef JOB_CONTROL
  { "fg", fg_builtin, 1, "fg [job_spec]",
      "	Place JOB_SPEC in the foreground, and make it the current job.  If\n\
	JOB_SPEC is not present, the shell's notion of the current job is\n\
	used" },
#endif

  { "hash", hash_builtin, 1, "hash [-r] [name]",
      "	For each NAME, the full pathname of the command is determined\n\
	and remembered.  The -r option causes the shell to forget all\n\
	remembered locations.  if no arguments are given, information\n\
	about remembered commands is presented" },

  { "help", help_builtin, 1, "help [pattern]",
     "	Display helpful information about builtin commands.  If\n\
	PATTERN is specified, gives detailed help on all commands\n\
	matching PATTERN, otherwise a list of the builtins is\n\
	printed" },

  { "history", history_builtin, 1, "history [n] [ [-w -r] [filename]]",
      "	Display the history list with line numbers.  Lines listed with\n\
	with a `*' have been modified.  Argument of N says to list only\n\
	the last N lines.  Argument `-w' means write out the current\n\
	history file.  `-r' means to read it instead.  If FILENAME is\n\
	given, then use that file, else if $HISTFILE has a value, use\n\
	that, else use ~/.bash_history" },

#ifdef JOB_CONTROL
  { "jobs", jobs_builtin, 1, "jobs [-l]",
      "	Lists the active jobs; given the -l options lists process id's\n\
	in addition to the normal information" },

  { "kill", kill_builtin, 1, "kill [-sigspec -l] [pid | job ...]",
      "	Send the processes named by PID (or JOB) the signal SIGSPEC.\n\
	If SIGSPEC is not present, then SIGTERM is assumed.  An argument\n\
	of `-l' lists the signal names.  Kill is a builtin for two reasons;\n\
	it allows job id's to be used instead of pids, and if you run out of\n\
	processes, you can still kill them" },
#endif

  { "local", local_builtin, 1, "local name[=value]",
      "	Create a local variable called NAME, and give it VALUE.  LOCAL\n\
	can only be used within a function; it makes the variable NAME\n\
	have a visible scope restricted to that function and its children" },

  { "logout", logout_builtin, 1, "logout",
      "	Logout of a login shell" },

#ifdef PUSHD_AND_POPD
  { "popd", popd_builtin, 1, "popd [+n]",
      "	Pops the directory stack, and cd's to the new top directory.\n\
	The elements are numbered from 0 starting at the first directory\n\
	listed with `dirs'; i.e. `popd' is equivalent to `popd 0'" },

  { "pushd", pushd_builtin, 1, "pushd [dir | +n]",
      "	Save the current directory on a list and then CD to DIR.\n\
	With no arguments, exchanges the top two directories.\n\
\n\
	+n   Brings the Nth directory to the top of the list by rotating.\n\
\n\
	dir  Makes the current working directory be the top of\n\
	     the stack, and then cd's to DIR.\n\
\n\
	You can see the saved directory list with the `dirs' command" },
#endif  /* PUSHD_AND_POPD */

  { "pwd", pwd_builtin, 1, "pwd",
      "	Print the current working directory"},

  { "read", read_builtin, 1, "read [name ...]",
      "	One line is read from the standard input, and the first word\n\
	is assigned to the first NAME, the second word to the second NAME,\n\
	etc. with leftover words assigned to the last NAME.  Only the\n\
	characters in $IFS are recognized as word delimiters.  The return\n\
	code is zero, unless end-of-file is encountered" },

  { "readonly", readonly_builtin, 1, "readonly [name ...]",
      "	The given NAMEs are marked readonly and the values of these NAMEs\n\
	may not be changed by subsequent assignment.  If no arguments are\n\
	given, a list of all readonly names is printed" },

  { "return", return_builtin, 1,  "return [n]",
      "	Causes a function to exit with the return value specified by N.\n\
	If N is omitted, the return status is that of the last command" },

  { "set", set_builtin, 1, "set [-aefhkntuvx] [arg ...]",
      "	-a  Mark variables which are modified or created for export\n\
	-e  Exit immediately if a command exits with a non-zero status\n\
	-f  Disable file name generation (globbing)\n\
	-h  Locate and remember function commands as functions are\n\
	    defined.  Functions commands are normally looked up when\n\
	    the function is executed)\n\
	-k  All keyword arguments are placed in the environment for a\n\
	    comand, not just those that precede the command name\n\
	-n  Read commands but do not execute them\n\
	-t  Exit after reading and executing one command\n\
	-u  Treat unset variables as an error when substituting\n\
	-v  Print shell input lines as they are read\n\
	-x  Print commands and their arguments as they are executed\n\
	-l  Save and restore the binding of the NAME in a FOR command.\n\
	-d  Disable the hashing of commands that are looked up for execution.\n\
	    Normally, commands are remembered in a hash table, and once\n\
	    found, do not have to be looked up again\n\
	-o  Enable ! style history substitution.  This flag is on by\n\
	    by default.\n\
\n\
	Using + rather than - causes these flags to be turned off.  The\n\
	flags can also be used upon invocation of the shell.  The current\n\
	set of flags may be found in $-.  The remaining ARGs are positional\n\
	parameters and are assigned, in order, to $1, $2, ... $9.  If no\n\
	ARGs are given, all shell variables are printed" },

  { "shift", shift_builtin, 1, "shift [n]",
      "	The positional parameters from $N+1 ... are renamed to $1 ....  If\n\
	N is not given, it is assumed to be 1" },

  { "source", period_builtin, 1, "source <file>",
      " An alias for the `.' builtin" },

#ifdef JOB_CONTROL
  { "suspend", suspend_builtin, 1, "suspend [-f]",
      "	Suspend the execution of this shell until it recieves a SIGCONT\n\
	signal.  The `-f' if specified says not to complain about this\n\
	being a login shell if it is; just suspend anyway" },
#endif

  { "[", test_builtin, 1, "[ args... ]",
      "	Synonym for `test'" },

  { "test", test_builtin, 1, "test [expr]",
      "	Exits with a status of 0 (trueness) or 1 (falseness) depending on\n\
	the evaluation of EXPR.  Expressions may be unary or binary.  Unary\n\
	expressions are often used to examine the status of a file.  There\n\
	are string operators as well, and numeric comparison operators.\n\
\n\
	File operators:\n\
\n\
	-b FILE		True if file is block special.\n\
	-c FILE		True if file is character special.\n\
	-d FILE		True if file is a directory.\n\
	-ef FILE	True if file is a hard link.\n\
	-f FILE		True if file is a plain file.\n\
	-g FILE		True if file is set-group-id.\n\
	-L FILE		True if file is a symbolic link.\n\
	-k FILE		True if file has its \"sticky\" bit set.\n\
	-p FILE		True if file is a named pipe.\n\
	-r FILE		True if file is readable by you.\n\
	-s FILE		True if file is not empty.\n\
	-S FILE		True if file is a socket.\n\
	-t [FD]		True if FD is opened on a terminal.  If FD\n\
			is omitted, it defaults to 1 (stdout).\n\
	-u FILE		True if the file is set-user-id.\n\
	-w FILE		True if the file is writable by you.\n\
	-x FILE		True if the file is executable by you.\n\
	-O FILE		True if the file is effectively owned by you.\n\
\n\
	FILE1 -nt FILE2	True if file1 is newer than (according to\n\
			modification date) file2.\n\
\n\
	FILE1 -ot FILE2 True if file1 is older than file2.\n\
\n\
	String operators:\n\
\n\
	-z STRING	True if string is empty.\n\
	-n STRING\n\
   or			True if string is not empty.\n\
	STRING\n\
	STRING1 = STRING2\n\
			True if the strings are equal.\n\
	STRING1 != STRING2\n\
			True if the strings are not equal.\n\
\n\
	Other operators:\n\
\n\
	! EXPR		True if expr is false.\n\
	EXPR1 -a EXPR2	True if both expr1 AND expr2 are true.\n\
	EXPR1 -o EXPR2	True if either expr1 OR expr2 is true.\n\
\n\
	arg1 OP arg2\n\
	OP is one of -eq, -ne, -lt, -le, -gt, ge.\n\
			Arithmetic binary operators return true if ARG1\n\
			is equal, not-equal, less-than, less-than-or-equal,\n\
			greater-than, or greater-than-or-equal than arg2" },

  { "times", times_builtin, 1, "times",
      "	Print the accumulated user and system times for processes run from\n\
	the shell" },

  { "trap", trap_builtin, 1, "trap [arg] [signal_spec]",
      "	The command ARG is to be read and executed when the shell receives\n\
	signal(s) SIGNAL_SPEC.  If ARG is absent all specified signals are\n\
	are reset to their original values.  If ARG is the null string this\n\
	signal is ignored by the shell and by the commands it invokes.  If\n\
	SIGNAL_SPEC is ON_EXIT (0) the command ARG is executed on exit from\n\
	the shell.  The trap command with no arguments prints the list of\n\
	commands associated with each signal number.  SIGNAL_SPEC is either\n\
	a signal name in <signal.h>, or a signal number.  The syntax `trap -l'\n\
	prints a list of signal names and their corresponding numbers.\n\
	Note that a signal can be sent to the shell with \"kill -signal $$\"" },

  { "type", type_builtin, 1, "type [-all] [-type | -path] [name ...]",
      "	For each NAME, indicate how it would be interpreted if used as a\n\
	command name.\n\
\n\
	If the -type flag is used, returns a single word which is one of\n\
	`alias', `function', `builtin', `file' or `', if NAME is an\n\
	alias, shell function, shell builtin, disk file, or unfound,\n\
	respectively.\n\
\n\
	If the -path flag is used, either returns the name of the disk file\n\
	that would be exec'ed, or nothing if -type wouldn't return `file'\n\
\n\
	If the -all flag is used, returns all of the places that contain\n\
	an executable named `file'.  This includes the aliases and functions,\n\
	if and only if the -path flag is not also used" },

  { "typeset", declare_builtin, 1, "typeset [-frx] [name[=word]]",
      "	Obsolete.  See `declare'" },

  { "ulimit", ulimit_builtin, 1, "ulimit [-cdmstf [limit]]",
      " Ulimit provides control over the resources available to processes\n\
        started by the shell, on systems that allow such control.  If an\n\
        option is given, it is interpreted as follows:\n\
\n\
                -c      the maximum size of core files created\n\
                -d      the maximum size of a process's data segment\n\
                -m      the maximum resident set size\n\
                -s      the maximum stack size\n\
                -t      the maximum amount of cpu time in seconds\n\
                -f      the maximum size of files created by the shell\n\
\n\
        If LIMIT is given, it is the new value of the specified resource.\n\
	Otherwise, the current value of the specified resource is printed.\n\
	If no option is given, then -f is assumed.  Values are in 1k\n\
	increments, except for -t, which is in seconds" },

  { "umask", umask_builtin, 1, "umask [nnn]",
      "	The user file-creation mask is set to NNN.  If NNN is omitted, the\n\
	current value of the mask is printed.  NNN is read as an octal\n\
	number" },

  { "unalias", unalias_builtin, 1, "unalias [name ...]",
      "	Remove NAMEs from the list of defined aliases" },

  { "unset", unset_builtin, 1, "unset [name ...]",
      "	For each NAME, remove the corresponding variable or function.  Note\n\
	that PATH and IFS cannot be unset" },

  { "wait", wait_builtin, 1, "wait [n]",
      "	Wait for the specified process and report its termination\n\
	status.  If N is not given, all currently active child processes\n\
	are waited for, and the return code is zero" },

  /* This marks the end of the functions which are builtins per-se.  The
     following are actually parser constructs. */
  { "for", (Function *)0x0, 1, "for NAME [in WORDS ...] ; do COMMANDS ; done",
      "	The `for' loop executes a sequence of commands for each member in a\n\
	list of items.  If \"in WORDS ...\" is not present, then \"in $*\" is\n\
	assumed.  For each element in WORDS, NAME is set to that element, and\n\
	the COMMANDS are executed" },

  { "case", (Function *)0x0, 1, "case WORD in [PATTERN [| PATTERN ...]) COMMANDS ; ...] esac",
      "	Selectively execute COMMANDS based upon WORD matching PATTERN.  The\n\
	`|' is used to separate multiple patterns" },

  { "if", (Function *)0x0, 1, "if COMMANDS then COMMANDS [else COMMANDS] fi",
      "	`if' executes the `then' COMMANDS only if the final command in the `if'\n\
	COMMANDS has an exit status of zero" },

  { "while", (Function *)0x0, 1, "while COMMANDS do COMMANDS done",
      "	Expand and execute COMMANDS as long as the final command in the `while'\n\
	COMMANDS has an exit status of zero" },

  { "until", (Function *)0x0, 1, "until COMMAND do COMMANDS done",
      "	Expand and execute COMMANDS as long as the final command in the `until'\n\
	COMMANDS has an exit status which is not zero" },

  { "function", (Function *)0x0, 1, "function NAME { COMMANDS ; } or NAME () { COMMANDS ; }",
      "	Create a simple command invoked by NAME which runs COMMANDS.  Arguments\n\
	on the command line along with NAME are passed to the function as\n\
	$0 .. $n" },
  { "{ ... }", (Function *)0x0, 1, "{ COMMANDS }",
      "	Run a set of commands in a group.  This is one way to redirect an\n\
	entire set of commands" },

#ifdef JOB_CONTROL
  { "%", (Function *)0x0, 1, "%[DIGITS | WORD] [&]",
      "	This is similar to the `fg' command.  Resume a stopped or background\n\
	job.  If you specifiy DIGITS, then that job is used.  If you specify\n\
	WORD, then the job whose name begins with WORD is used.  Following\n\
	the job specification with a `&' places the job in the background" },
#endif

  { (char *)0x0, (Function *)0x0, 0, (char *)0x0, (char *)0x0 }

};

/* Enable the shell command NAME.  If DISABLE_P is non-zero, then
   disable NAME instead. */
enable_shell_command (name, disable_p)
     char *name;
     int disable_p;
{
  register int i;
  int found = 0;

  for (i = 0; shell_builtins[i].function; i++)
    if (strcmp (name, shell_builtins[i].name) == 0)
      {
	found++;
	shell_builtins[i].enabled = !disable_p;
      }

  return (found);
}

/* Enable/disable shell commands present in LIST.  If list is not specified,
   then print out a list of shell commands showing which are enabled and
   which are disabled. */
enable_builtin (list)
     WORD_LIST *list;
{
  int result = 0;

  if (!list)
    {
      register int i;

      for (i = 0; shell_builtins[i].function; i++)
	printf ("enable %s%s\n", shell_builtins[i].enabled ? "" : "-n ",
		shell_builtins[i].name);
    }
  else
    {
      int disable_p = (strcmp (list->word->word, "-n") == 0);

      if (disable_p)
	list = list->next;

      while (list)
	{
	  result = enable_shell_command (list->word->word, disable_p);
	  list = list->next;
	}
    }
  return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
}

/* Print out a list of the known functions in the shell, and what they do.
   If LIST is supplied, print out the list which matches for each pattern
   specified. */
help_builtin (list)
     WORD_LIST *list;
{
  if (!list)
    {
      register int j, i = 0;
      char blurb[256];

      show_shell_version ();
      printf (
"Shell commands that are defined internally.  Type `help' to see this list.\n\
Type `help name' to find out more about the function `name'.\n\
Use `info bash' to find out more about the shell in general.\n\
\n\
A star (*) next to a name means that the command is disabled.\n\
\n");

      for (i = 0; shell_builtins[i].name; i++)
	{
	  sprintf (blurb, "%c%s", shell_builtins[i].enabled ? ' ' : '*',
		   shell_builtins[i].short_doc);

	  blurb[35] = '\0';
	  printf ("%s", blurb);

	  if (i % 2)
	    printf ("\n");
	  else
	    for (j = strlen (blurb); j < 35; j++)
	      putc (' ', stdout);
	    
	}
      if (i % 2)
	printf ("\n");
    }
  else
    {
      int match_found = 0;
      char *pattern = "";
      
      if (glob_pattern_p (list->word->word))
	{
	  printf ("Shell commands matching keyword%s `",
		  list->next ? "s" : "");
	  print_word_list (list, ", ");
	  printf ("'\n\n");
	}
      while (list)
	{
	  register int i = 0, plen;

	  pattern = list->word->word;
	  plen = strlen (pattern);

	  while (shell_builtins[i].name)
	    {
	      if (glob_match (pattern, shell_builtins[i].name, 0) ||
		  strnicmp (pattern, shell_builtins[i].name, plen) == 0)
		{
		  printf ("%s: %s\n%s.\n",
			  shell_builtins[i].name, shell_builtins[i].short_doc,
			  shell_builtins[i].long_doc);
		  match_found++;
		}
	      i++;
	    }
	  list = list->next;
	}
      if (!match_found)
	{
	  printf ("No help topics match `%s'.  Try `help help'.\n", pattern);
	  fflush (stdout);
	  return (EXECUTION_FAILURE);
	}
    }
  fflush (stdout);
  return (EXECUTION_SUCCESS);
}

/* Do nothing.  This command is a no-op. */
colon_builtin ()
{
  return (EXECUTION_SUCCESS);
}

/* Read and execute commands from the file passed as argument.  Guess what.
   This cannot be done in a subshell, since things like variable assignments
   take place in there.  So, I open the file, place it into a large string,
   close the file, and then execute the string. */
period_builtin (list)
     WORD_LIST *list;
{
  if (!list)
    return (EXECUTION_SUCCESS);
  else
    {
      int fd;			/* File descriptor. */
      int tt;			/* Temporary result. */
      char *string;		/* String to execute. */
      char *filename, *tempfile;
      extern char *find_path_file ();
      struct stat finfo;

      tempfile = find_path_file (list->word->word);

      if (!tempfile)
	tempfile = savestring (list->word->word);

      filename = (char *)alloca (1 + strlen (tempfile));
      strcpy (filename, tempfile);
      free (tempfile);

      if (stat (filename, &finfo) == -1 ||
	  (fd = open (filename, O_RDONLY)) == -1)
	goto file_error_exit;

      string = (char *)xmalloc  (1 + finfo.st_size);
      tt = read (fd, string, finfo.st_size);

      /* Close the open file, preserving the state of errno. */
      { int temp = errno; close (fd); errno = temp; }

      if (tt != finfo.st_size)
	{
	  free (string);
	file_error_exit:
	  file_error (filename);
	  return (EXECUTION_FAILURE);
	}
      remember_args (list->next, 1);
      string[finfo.st_size] = '\0';
      return (parse_and_execute (string, filename));
    }
}

/* Parse and execute the commands in STRING.  Returns whatever
   execute_command () returns.  This frees STRING. */
parse_and_execute (string, from_file)
     char *string;
     char *from_file;
{
  extern int interactive, remember_on_history;
  extern int history_expansion;
  extern int indirection_level;
  char *indirection_level_string ();

  int old_interactive = interactive;
  int old_remember_on_history = remember_on_history;
  int old_history_expansion = history_expansion;
  int last_result = EXECUTION_SUCCESS;
  char *orig_string = string;
  extern COMMAND *global_command;

  push_stream ();
  interactive = 0;
  indirection_level++;

  /* We don't remember text read by the shell this way on
     the history list, and we don't use !$ in shell scripts. */
  remember_on_history = 0;
  history_expansion = 0;
  
  with_input_from_string (string, from_file);
  {
    extern char *yy_input_dev;
    COMMAND *command;

    while (*yy_input_dev)
      {
	if (interrupt_state)
	  {
	    last_result = EXECUTION_FAILURE;
	    break;
	  }

	if (yyparse () == 0)
	  {
	    if ((command = global_command) != (COMMAND *)NULL)
	      {
		global_command = (COMMAND *)NULL;

		if (echo_command_at_execute)
		  fprintf (stderr, "%s %s\n",
			   indirection_level_string (),
			   make_command_string (command));

		last_result = execute_command (command);
		dispose_command (command);
	      }
	  }
	else
	  {
	    last_result = EXECUTION_FAILURE;

	    /* Since we are shell compatible, syntax errors in a script
	       abort the execution of the script.  Right? */
	    break;
	  }
      }
  }

  remember_on_history = old_remember_on_history;
  history_expansion = old_history_expansion;
  interactive = old_interactive;
  pop_stream ();
  indirection_level--;
  if (orig_string)
    free (orig_string);

  return (last_result);
}

/* Set up to break x levels, where x defaults to 1, but can be specified
   as the first argument. */

/* The depth of while's and until's. */
int loop_level = 0;

/* Non-zero when a "break" instruction is encountered. */
int breaking = 0;

/* Define BREAK_COMPLAINS if you want the incompatible, but useful
   error messages about `break' and `continue' out of context. */
#define BREAK_COMPLAINS

/* Return non-zero if a break or continue command would be okay.
   Print an error message if break or continue is meaningless here. */
check_loop_level ()
{
  extern char *this_command_name;

#ifdef BREAK_COMPLAINS
  if (!loop_level)
    report_error ("%s: only meaningful in a `for', `while', or `until' loop",
		  this_command_name);
#endif
  return (loop_level);
}

break_builtin (list)
     WORD_LIST *list;
{

  if (!check_loop_level ())
    return (EXECUTION_FAILURE);

  breaking = get_numeric_arg ("break", list);

  if (breaking < 0)
    breaking = 0;

  if (breaking > loop_level)
    breaking = loop_level;

  return (EXECUTION_SUCCESS);
}

/* Set up to continue x levels, where x defaults to 1, but can be specified
   as the first argument. */

/* Non-zero when we have encountered a continue instruction. */
int continuing = 0;

continue_builtin (list)
     WORD_LIST *list;
{

  if (!check_loop_level ())
    return (EXECUTION_FAILURE);

  continuing = get_numeric_arg ("continue", list);

  if (continuing < 0)
    continuing = 0;

  if (continuing > loop_level)
    continuing = loop_level;
  
  return (EXECUTION_SUCCESS);
}

/* Read a numeric arg FOR_WHOM, the name of the shell builtin that wants
   it.  LIST is the word list that the arg is to come from. */
get_numeric_arg (for_whom, list)
     char *for_whom;
     WORD_LIST *list;
{
  int count = 1;

  if (list)
    {
      if (sscanf (list->word->word, "%d", &count) != 1)
	{
	  report_error ("%s: bad non-numeric arg `%s'",
			for_whom, list->word->word);
	  longjmp (top_level, DISCARD);
	}
      no_args (for_whom, list->next);
    }
  return (count);
}

/* Change the current working directory to the first word in LIST, or
   to $HOME if there is no LIST.  Do nothing in the case that there is
   no $HOME nor LIST. If the variable CDPATH exists, use that as the search
   path for finding the directory.  If all that fails, and the 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. */

/* By default, follow the symbolic links as if they were real directories
   while hacking the `cd' command.  This means that `cd ..' moves up in
   the string of symbolic links that make up the current directory, instead
   of the absolute directory.  The shell variable `nolinks' controls this
   flag. */
int follow_symbolic_links = 1;

/* In order to keep track of the working directory, we have this static
   variable hanging around. */
static char *the_current_working_directory = (char *)NULL;

cd_builtin (list)
     WORD_LIST *list;
{
  char *dirname;

#ifdef FACIST
  {
    extern int restricted;
    if (restricted)
      {
	report_error ("cd: privileged command");
	return (EXECUTION_FAILURE);
      }
  }
#endif

  if (list)
    {
      char *extract_colon_unit ();
      char *path_string = get_string_value ("CDPATH");
      char *path;
      int index = 0;

      dirname = list->word->word;

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

	      if (!disallow_filename_globbing && *path)
		{
		  char *tilde_expand (), *te_string = tilde_expand (path);

		  free (path);
		  path = te_string;
		}

	      dir = (char *)alloca (2 + strlen (dirname) + strlen (path));

	      if (!dir)
		abort ();

	      if (!*path)
		{
		  free (path);
		  path = savestring ("."); /* by definition. */
		}

	      strcpy (dir, path);
	      if (path[strlen (path) - 1] != '/')
		strcat (dir, "/");
	      strcat (dir, dirname);
	      free (path);

	      if (change_to_directory (dir))
		{
		  if (strncmp (dir, "./", 2) != 0)
		    printf ("%s\n", dir);
		  dirname = dir;

		  goto bind_and_exit;
		}
	    }
	}

      if (!change_to_directory (dirname))
	{
	  /* 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 *get_working_directory (), *get_string_value ();
	char *directory = get_working_directory ("cd");

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

	if (directory)
	  free (directory);
      }
      return (EXECUTION_SUCCESS);
    }
}

/* Do the work of changing to the directory NEWDIR.  Handle symbolic
   link following, etc. */
change_to_directory (newdir)
     char *newdir;
{
  char *get_working_directory (), *make_absolute ();
  char *t;

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

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

      /* Get rid of trailing `/'. */
      {
	register int len_t = strlen (t);
	if (len_t > 1)
	  {
	    --len_t;
	    if (t[len_t] == '/')
	      t[len_t] = '\0';
	  }
      }

      if (chdir (t) < 0)
	{
	  free (t);
	  return (0);
	}

      if (the_current_working_directory)
	strcpy (the_current_working_directory, t);

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

/* Return a consed string which is the current working directory.
   FOR_WHOM is the name of the caller for error printing.  */
char *
get_working_directory (for_whom)
     char *for_whom;
{
  if (!follow_symbolic_links)
    {
      if (the_current_working_directory)
	free (the_current_working_directory);

      the_current_working_directory = (char *)NULL;
    }

  if (!the_current_working_directory)
    {
      char *directory, *getwd ();

      the_current_working_directory = (char *)xmalloc (MAXPATHLEN);
      directory = getwd (the_current_working_directory);
      if (!directory)
	{
	  report_error ("%s: %s", for_whom, the_current_working_directory);
	  free (the_current_working_directory);
	  the_current_working_directory = (char *)NULL;
	  return (char *)NULL;
	}
    }

  return (savestring (the_current_working_directory));
}


/* Print the words in LIST to standard output.  If the first word is
   `-n', then don't print a trailing newline.  We also support the
   echo syntax from Version 9 unix systems. */
echo_builtin (list)
     WORD_LIST *list;
{
  int display_return = 1, do_v9 = 0;

  while (list && list->word->word[0] == '-')
    {
      char *temp = &(list->word->word[1]);

      while (*temp)
	{
	  if (*temp == 'n')
	    display_return = 0;
#ifdef V9_ECHO
	  else if (*temp == 'e')
	    do_v9 = 1;
#endif
	  else
	    goto just_echo;
	  
	  temp++;
	}
      list = list->next;
    }

just_echo:

  if (list)
    {
#ifdef V9_ECHO
      if (do_v9)
	{
	  while (list)
	    {
	      register char *s = list->word->word;
	      register int c;

	      while (c = *s++)
		{
		  if (do_v9 && c == '\\')
		    {
		      switch (c = *s++)
			{
			case 'b': c = '\b'; break;
			case 'c': display_return = 0; continue;
			case 'f': c = '\f'; break;
			case 'n': c = '\n'; break;
			case 'r': c = '\r'; break;
			case 't': c = '\t'; break;
			case 'v': c = (int) 0x0B; break;
			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
			  c -= '0';
			  if (*s >= '0' && *s <= '7')
			    c = c * 8 + (*s++ - '0');
			  if (*s >= '0' && *s <= '7')
			    c = c * 8 + (*s++ - '0');
			  break;
			case '\\': break;
			default:  putchar ('\\'); break;
			}
		    }
		  putchar(c);
		}
	      list = list->next;
	      if (list)
		putchar(' ');
	    }
	}
      else
#endif  /* V9_ECHO */	
	print_word_list (list, " ");
    }
  if (display_return)
    printf ("\n");
  fflush (stdout);
  return (EXECUTION_SUCCESS);
}

/* Parse the string that these words make, and execute the command found. */
eval_builtin (list)
     WORD_LIST *list;
{
  char *string_list ();
  int result;

  /* Note that parse_and_execute () free ()'s what it is passed. */
  if (list)
    result = parse_and_execute (string_list (list), "eval");
  else
    result = EXECUTION_SUCCESS;
  return (result);
}

/* Defined in execute_cmd.c */
extern REDIRECT *redirection_undo_list;

exec_builtin (list)
     WORD_LIST *list;
{

  maybe_make_export_env ();

  /* First, let the redirections remain. */
  dispose_redirects (redirection_undo_list);
  redirection_undo_list = (REDIRECT *)NULL;

  if (!list)
    return (EXECUTION_SUCCESS);
  else {
    /* Otherwise, execve the new command with args. */
    char *command, **args;
    int dash_name = 0;

    if (strcmp (list->word->word, "-") == 0) {
      /* The user would like to exec this command as if it was a
	 login command.  Do so. */
      list = list->next;
      dash_name++;
    }

    args = (char **)make_word_array (list);
    command = (char *)find_user_command (args[0]);
    if (!command) {
      report_error ("%s: not found", args[0]);
      goto failed_exec;
    }

    command = (char *)full_pathname (command);
    /* If the user wants this to look like a login shell, then
       pass the full pathname in argv[0]. */
    if (dash_name) {
      char *new_name = (char *)xmalloc (1 + strlen (command));
      strcpy (new_name, "-");
      strcat (new_name, command);
      args[0] = new_name;
    }

    write_history (get_string_value ("HISTFILE"));

    signal (SIGINT, SIG_DFL);
    signal (SIGQUIT, SIG_DFL);
    execve (command, args, export_env);

    if (!executable_file (command))
      report_error ("%s: cannot execute", command);
    else
      file_error (command);

  failed_exec:
    if (!find_variable ("no_exit_on_failed_exec"))
      exit (EXECUTION_FAILURE);
    return (EXECUTION_FAILURE);
  }
}

exit_builtin (list)
     WORD_LIST *list;
{
  extern int login_shell, interactive;

  if (interactive && login_shell)
    report_error ("logout");

  exit_or_logout (list);
}

/* How to logout. */
logout_builtin (list)
     WORD_LIST *list;
{
  if (!login_shell && interactive)
    {
      report_error ("Not login shell: use `exit' or `bye'");
      return (EXECUTION_FAILURE);
    }
  else
    exit_or_logout (list);
}

/* Clean up work for exiting or logging out. */
Function *last_shell_builtin = (Function *)NULL;
Function *this_shell_builtin = (Function *)NULL;

exit_or_logout (list)
     WORD_LIST *list;
{
  extern int last_command_exit_value;

#ifdef JOB_CONTROL
  int exit_immediate_okay;

  exit_immediate_okay = (!interactive ||
			 last_shell_builtin == exit_builtin ||
			 last_shell_builtin == logout_builtin ||
			 last_shell_builtin == jobs_builtin);

  /* Check for stopped jobs if the user wants to. */
  if (1 && !exit_immediate_okay)
    {
      register int i;
      for (i = 0; i < job_slots; i++)
	if (jobs[i] && (jobs[i]->state == JSTOPPED))
	  {
	    report_error ("There are stopped jobs.");

	    /* This is NOT superfluous because EOF can get here without
	       going through the command parser. */
	    last_shell_builtin = exit_builtin;

	    longjmp (top_level, DISCARD);
	  }
    }
#endif

  /* Get return value if present.  This means that you can type
     `logout 5' to a shell, and it returns 5. */

  if (list)
    last_command_exit_value =
      get_numeric_arg (login_shell ? "logout" : "exit", list);

  /* Run our `~/.bash_logout' file if it exists, and this is a login shell. */
  if (login_shell)
    maybe_execute_file ("~/.bash_logout");

  /* Exit the program. */
  longjmp (top_level, EXITPROG);
}

/* For each variable name in LIST, make that variable appear in the
   environment passed to simple commands.  If there is no LIST, then
   print all such variables.  An argument of `-n' says to remove the
   exported attribute from variables named in LIST. */
export_builtin (list)
     register WORD_LIST *list;
{
  return (set_or_show_attributes (list, att_exported));
}

/* For each variable name in LIST, make that variable have the specified
   ATTRIBUTE.  An arg of `-n' says to remove the attribute from the the
   remaining names in LIST.  */
set_or_show_attributes (list, attribute)
     register WORD_LIST *list;
     int attribute;
{
  register SHELL_VAR *var;
  int assign, undo = 0;
  extern int array_needs_making;

  if (list)
    {
      while (list)
	{
	  register char *name = list->word->word;

	  if (strcmp (name, "-n") == 0)
	    {
	      undo++;
	      list = list->next;
	      continue;
	    }

	  if ((assign = assignment (name)) != 0)
	    {
	      do_assignment (name);
	      name[assign] = '\0';
	    }

	  if (undo)
	    {
	      var = find_variable (name);
	      if (var)
		var->attributes &= ~attribute;
	    }
	  else
	    {
	      var = find_variable (name);

	      if (!var)
		{
		  var = bind_variable (name, "");
		  var->attributes |= att_invisible;
		}

	      var->attributes |= attribute;
	    }
	  array_needs_making++;

	  list = list->next;
	}
    }
  else
    {
      var = variable_list;

      while (var)
	{
	  if ((var->attributes & attribute) && !invisible_p (var))
	    {
	      char flags[5];

	      flags[0] = '\0';

	      if (var->attributes & att_exported)
		strcat (flags, "x");

	      if (var->attributes & att_readonly)
		strcat (flags, "r");

	      if (var->attributes & att_function)
		 strcat (flags, "f");

	      if (flags[0])
		{
		  printf ("declare -%s ", flags);

		  if (!function_p (var))
		    printf ("%s=%s\n", var->name, var->value);
		  else
		    {
		      char *named_function_string ();

		      printf ("%s\n", named_function_string (var->name,
							     var->value, 1));
		    }
		}
	    }
	  var = var->next;
	}
    }
  return (EXECUTION_SUCCESS);
}

/* Hashing filenames in the shell. */

#include "hash.h"

#define FILENAME_HASH_BUCKETS 631

HASH_TABLE *hashed_filenames;

typedef struct {
  /* The full pathname of the file. */
  char *path;

  /* Whether `.' appeared before this one in $PATHS. */
  int check_dot;
} PATH_DATA;

#define pathdata(x) ((PATH_DATA *)(x)->data)

initialize_filename_hashing ()
{
  hashed_filenames = make_hash_table (FILENAME_HASH_BUCKETS);
}

/* Place FILENAME (key) and FULL_PATHNAME (data->path) into the
   hash table.  CHECK_DOT if non-null is for future calls to
   find_hashed_filename (). */
remember_filename (filename, full_pathname, check_dot)
     char *filename, *full_pathname;
     int check_dot;
{
  register BUCKET_CONTENTS *item;

  if (hashing_disabled)
    return;
  item = add_hash_item (filename, hashed_filenames);
  if (item->data)
    free (pathdata(item)->path);
  else
    item->data = (char *)xmalloc (sizeof (PATH_DATA));

  item->key = savestring (filename);
  pathdata(item)->path = savestring (full_pathname);
  pathdata(item)->check_dot = check_dot;
  item->times_found = 0;
}

/* Temporary static. */
char *dotted_filename = (char *)NULL;

/* Return the full pathname that FILENAME hashes to.  If FILENAME
   is hashed, but data->check_dot is non-zero, check ./FILENAME
   and return that if it is executable. */
char *
find_hashed_filename (filename)
     char *filename;
{
  register BUCKET_CONTENTS *item;

  if (hashing_disabled)
    return ((char *)NULL);

  item = find_hash_item (filename, hashed_filenames);
  if (item) {

    /* If this filename is hashed, but `.' comes before it in the path,
       then see if `./filename' is an executable. */
    if (pathdata(item)->check_dot) {

      if (dotted_filename)
	free (dotted_filename);

      dotted_filename = (char *)xmalloc (3 + strlen (filename));
      strcpy (dotted_filename, "./");
      strcat (dotted_filename, filename);

      if (executable_file (dotted_filename))
	return (dotted_filename);

      /* Watch out.  If this file was hashed to "./filename", and
	 "./filename" is not executable, then return NULL. */
      if (strcmp (pathdata(item)->path, dotted_filename) == 0)
	return ((char *)NULL);
    }
    return (pathdata(item)->path);
  } else {
    return ((char *)NULL);
  }
}

/* Print statistics on the current state of hashed commands.  If LIST is
   not empty, then rehash (or hash in the first place) the specified
   commands. */
hash_builtin (list)
     WORD_LIST *list;
{
  extern Function *find_shell_builtin ();

  if (hashing_disabled)
    {
      report_error ("hashing is disabled");
      return (EXECUTION_FAILURE);
    }
  if (!list)
    {
      /* Print information about current hashed info. */
      int any_printed = 0;
      int bucket = 0;
      register BUCKET_CONTENTS *item_list;

      while (bucket < hashed_filenames->nbuckets)
	{
	  item_list = get_hash_bucket (bucket, hashed_filenames);
	  if (item_list)
	    {
	      if (!any_printed)
		{
		  printf ("hits\tcommand\n");
		  any_printed++;
		}
	      while (item_list)
		{
		  printf ("%4d\t%s\n",
			  item_list->times_found, pathdata(item_list)->path);
		  item_list = item_list->next;
		}
	    }
	  bucket++;
	}
      if (!any_printed)
	{
	  printf ("No commands in hash table.\n");
	}
    }
  else
    {
      /* Add or rehash the specified commands. */
      char *word;
      char *full_path;
      SHELL_VAR *var;

      if (strcmp (list->word->word, "-r") == 0)
	{
	  int bucket = 0;
	  register BUCKET_CONTENTS *item_list, *prev;

	  while (bucket < hashed_filenames->nbuckets)
	    {
	      item_list = get_hash_bucket (bucket, hashed_filenames);
	      if (item_list)
		{
		  while (item_list)
		    {
		      prev = item_list;
		      free (item_list->key);
		      free (pathdata(item_list)->path);
		      free (item_list->data);
		      item_list = item_list->next;
		      free (prev);
		    }
		  hashed_filenames->bucket_array[bucket] = (BUCKET_CONTENTS *)NULL;
		}
	      bucket++;
	    }
	  list = list->next;
	}
      while (list)
	{
	  word = list->word->word;
	  if (absolute_pathname (word))
	    goto next;
	  full_path = (char *)find_user_command (word);
	  var = find_variable (word);

	  if (!find_shell_builtin (word) &&
	      (!var || !function_p (var))) {
	    if (full_path && executable_file (full_path)) {
	      extern int dot_found_in_search;
	      remember_filename (word, full_path, dot_found_in_search);
	    } else {
	      report_error ("%s: not found", word);
	    }
	  }
	next:
	  list = list->next;
	}
    }
  return (EXECUTION_SUCCESS);
}

/* History.  Arg of -W FILENAME means write file, arg of -r FILENAME
   means read file.  Arg of N means only display that many items. */
history_builtin (list)
     WORD_LIST *list;
{
  HIST_ENTRY **hlist = history_list ();
  int limited = (list != (WORD_LIST *)NULL);
  int limit;
  register int i = 0;

  if (hlist)
    while (hlist[i]) i++;

  if (list)
    {
      if ((strcmp (list->word->word, "-w") == 0) ||
	  (strcmp (list->word->word, "-r") == 0)) {
	int writing = (strcmp (list->word->word, "-w") == 0);
	char *file;
	int result;

	if (list->next)
	  file = list->next->word->word;
	else
	  file = get_string_value ("HISTFILE");

	if (writing)
	  result = write_history (file);
	else
	  result = read_history (file);

	return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
      }
    }

  limit = get_numeric_arg ("history", list);
  if (limit < 0)
    limit = -limit;

  if (!limited)
    i = 0;
  else
    if ((i -= limit) < 0)
      i = 0;

  while (hlist[i])
    {
      fprintf (stdout,
	       "%5d%c %s\n", i + history_base, hlist[i]->data ? '*' : ' ',
	       hlist[i]->line);
      i++;
    }
  return (EXECUTION_SUCCESS);
}

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

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

  no_args ("pwd", list);

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

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

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

/* Read the value of the shell variables whose names follow.
   The reading is done from the current input stream, whatever
   that may be.  Successive words of the input line are assigned
   to the variables mentioned in LIST.  The last variable in LIST
   gets the remainder of the words on the line.

   S. R. Bourne's shell complains if you don't name a variable
   to receive the stuff that is read.  GNU's shell doesn't.  This
   allows you to let the user type random things without changing
   any variables in the shell. */
read_builtin (list)
     WORD_LIST *list;
{
  register char *varname;
  int size, c, i = 0, fildes;
  char *input_string, *ifs_chars;
  WORD_LIST *words, *rwords, *list_string ();
  FILE *input_stream;

  ifs_chars = get_string_value ("IFS");
  input_string = (char *)xmalloc (size = 128);

  /* We need unbuffered input from stdin.  So we make a new
     unbuffered stream with the same file descriptor, then
     unbuffer that one. */
  fildes = dup (fileno (stdin));

  if (fildes == -1)
    return (EXECUTION_FAILURE);

  input_stream = fdopen (fildes, "r");

  if (!input_stream)
    {
      close (fildes);
      return (EXECUTION_FAILURE);
    }

  setbuf (input_stream, (char *)NULL);

  while ((c = getc (input_stream)) != EOF)
    {
      if (i + 1 >= size)
	input_string = (char *)xrealloc (input_string, size += 128);

      input_string[i++] = c;

      if (c == '\n')
	{
	  if ((i >= 2) && (input_string[i - 2] == '\\'))
	    {
	      i -= 2;
	      continue;
	    }
	  break;
	}
    }
  input_string[i] = '\0';

  fclose (input_stream);

  if (!i)
    return (EXECUTION_FAILURE);    
  else
    {
      words = list_string (input_string, ifs_chars, 0);
      rwords = words;

      free (input_string);

      while (list)
	{
	  varname = list->word->word;

	  if (!list->next)
	    bind_variable (varname, words ? (char *)string_list (words) : "");
	  else
	    bind_variable (varname, words ? words->word->word : "");
	  stupidly_hack_special_variables (varname);
	  list = list->next;
	  if (words)
	    words = words->next;
	}
      if (rwords)
	dispose_words (rwords);
    }

  return (EXECUTION_SUCCESS);
}

/* For each variable name in LIST, make that variable read_only. */
readonly_builtin (list)
     register WORD_LIST *list;
{
  return (set_or_show_attributes (list, att_readonly));
}

/* If we are executing a user-defined function then exit with the value
   specified as an argument.  if no argument is given, then the last
   exit status is used. */
return_builtin (list)
     WORD_LIST *list;
{
  extern int last_command_exit_value;
  extern int return_catch_flag, return_catch_value;
  extern jmp_buf return_catch;

  return_catch_value = get_numeric_arg ("return", list);

  if (!list)
    return_catch_value = last_command_exit_value;

  if (return_catch_flag)
    longjmp (return_catch, 1);
  else
    {
      report_error ("can only `return' from a function");
      longjmp (top_level, DISCARD);
    }
}

/* Set some flags from the word values in the input list.  If LIST is empty,
   then print out the values of the variables instead.  If LIST contains
   non-flags, then set $1 - $9 to the successive words of LIST. */
set_builtin (list)
     WORD_LIST *list;
{
  int on_or_off, flag_name;

  if (!list)
    {
      print_var_list (variable_list);
      return (EXECUTION_SUCCESS);
    }

  /* Do the set command.  While the list consists of words starting with
     '-' or '+' treat them as flags, otherwise, start assigning them to
     $1 ... $n. */
  while (list)
    {
      char *string = list->word->word;
      if (strcmp (string, "-") == 0)
	{
	  WORD_LIST *t =
	    (WORD_LIST *)make_word_list (make_word ("-"), NULL);
	  remember_args (t, 1);
	  dispose_words (t);
	  return;
	}

      /* If the argument is `--' then signal the end of the list and
	 remember the remaining arguments. */
      if (strcmp (string, "--") == 0)
	{
	  list = list->next;
	  break;
	}

      if ((on_or_off = *string) &&
	  (on_or_off == '-' || on_or_off == '+'))
	{
	  int i = 1;
	  while (flag_name = string[i++])
	    {
	      if (flag_name == '?')
		{
		  /* Print all the possible flags. */
		}
	      else
		{
		  if (change_flag_char (flag_name, on_or_off) == FLAG_ERROR)
		    {
		      report_error ("%c%c: bad option", on_or_off, flag_name);
		      return (EXECUTION_FAILURE);
		    }
		}
	    }
	}
      else
	{
	  break;
	}
      list = list->next;
    }

  /* Assigning $1 ... $n */
  if (list)
    remember_args (list, 1);
  return (EXECUTION_SUCCESS);
}


/* **************************************************************** */
/*								    */
/*		    Pushing and Popping a Context		    */
/*								    */
/* **************************************************************** */

WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
int dollar_arg_stack_slots = 0;
int dollar_arg_stack_index = 0;

push_context ()
{
  extern int variable_context;

  push_dollar_vars ();
  variable_context++;
}

pop_context ()
{
  extern int variable_context;

  pop_dollar_vars ();
  kill_all_local_variables ();
  variable_context--;
}

/* Save the existing arguments on a stack. */
push_dollar_vars ()
{
  extern WORD_LIST *list_rest_of_args ();

  if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
    {
      if (!dollar_arg_stack)
	{
	  dollar_arg_stack_slots = 10;
	  dollar_arg_stack =
	    (WORD_LIST **)xmalloc (dollar_arg_stack_slots * sizeof (WORD_LIST *));
	}
      else
	{
	  dollar_arg_stack_slots += 10;
	  dollar_arg_stack =
	    (WORD_LIST **)xrealloc (dollar_arg_stack,
				    dollar_arg_stack_slots * sizeof (WORD_LIST **));
	}
    }
  dollar_arg_stack[dollar_arg_stack_index] = list_rest_of_args ();
  dollar_arg_stack[++dollar_arg_stack_index] = (WORD_LIST *)NULL;
}

pop_dollar_vars ()
{
  if (!dollar_arg_stack || !dollar_arg_stack_index)
    return;

  remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
}

/* Remember LIST in $0 ... $9, and REST_OF_ARGS.  If DESTRUCTIVE is
   non-zero, then discard whatever the existing arguments are, else
   only discard the ones that are to be replaced. */
remember_args (list, destructive)
     WORD_LIST *list;
     int destructive;
{
  register int i;

  for (i = 1; i < 10; i++)
    {
      if (destructive && dollar_vars[i])
	{
	  free (dollar_vars[i]);
	  dollar_vars[i] = (char *)NULL;
	}

      if (list)
	{
	  if (!destructive && dollar_vars[i])
	    free (dollar_vars[i]);

	  dollar_vars[i] = savestring (list->word->word);
	  list = list->next;
	}
    }

  /* If arguments remain, assign them to REST_OF_ARGS. */
  if (!list)
    {
      dispose_words (rest_of_args);
      rest_of_args = NULL;
    }
  else
    {
      rest_of_args = (WORD_LIST *)copy_word_list (list);
    }
}

/* Return if LIST is NULL else barf and jump to top_level.  COMMAND is
   the name of the command which is checking. */
no_args (command, list)
     char *command;
     WORD_LIST *list;
{
  if (list) {
    report_error ("%s: extra arguments", command);
    longjmp (top_level, DISCARD);
  }
}

/* Shift the arguments ``left''.  Shift DOLLAR_VARS down then take one
   off of REST_OF_ARGS and place it into DOLLAR_VARS[9].  If LIST has
   anything in it, it is a number which says where to start the shifting. */
shift_builtin (list)
     WORD_LIST *list;
{
  int times = get_numeric_arg ("shift", list);

  while (times-- > 0) {
    register int count;

    if (dollar_vars[1]) free (dollar_vars[1]);

    for (count = 1; count < 9; count++)
      dollar_vars[count] = dollar_vars[count + 1];

    if (rest_of_args) {
      WORD_LIST *temp = rest_of_args;

      dollar_vars[9] = savestring (temp->word->word);
      rest_of_args = rest_of_args->next;
      dispose_word (temp->word);
    } else {
      dollar_vars[9] = (char *)NULL;
    }
  }
}

/* TEST/[ builtin. */
test_builtin (list)
     WORD_LIST *list;
{
  char **argv;
  int argc, result;
  WORD_LIST *t = list;

  /* We let Matthew Bradburn and Kevin Braunsdorf's code do the
     actual test command.  So turn the list of args into an array
     of strings, since that is what his code wants. */

  if (!list)
    {
      if (strcmp (this_command_name, "[") == 0)
	report_error ("[: missing `]'\n");

      return (1);
    }

  /* Get the length of the argument list. */
  for (argc = 0; t; t = t->next, argc++);

  /* Account for argv[0] being a command name.  This makes our life easier. */
  argc++;
  argv = (char **)xmalloc ((1 + argc) * sizeof (char *));
  argv[argc] = (char *)NULL;

  /* this_command_name is the name of the command that invoked this
     function.  So you can't call test_builtin () directly from
     within this code, there are too many things to worry about. */
  argv[0] = savestring (this_command_name);

  for (t = list, argc = 1; t; t = t->next, argc++)
    argv[argc] = savestring (t->word->word);

  result = test_command (argc, argv);
  free_array (argv);
  return (result);
}

/* Print the totals for system and user time used.  The
   information comes from variables in jobs.c used to keep
   track of this stuff. */
times_builtin (list)
     WORD_LIST *list;
{
#ifndef SYSV

  extern long system_minutes_used, user_minutes_used;
  extern int system_seconds_used, user_seconds_used;

  no_args ("times", list);
  printf ("%0dm%0ds %0dm%0ds\n", system_minutes_used, system_seconds_used,
	  			 user_minutes_used, user_seconds_used);
#else
  struct tms t;

  no_args ("times", list);

  times(&t);

  printf("shell:    %02d:%02d(sys) %02d:%02d(usr)\n",
	t.tms_stime/60,t.tms_stime%60,
	t.tms_utime/60,t.tms_utime%60);
  printf("children: %02d:%02d(sys) %02d:%02d(usr)\n",
	t.tms_cstime/60,t.tms_cstime%60,
	t.tms_cutime/60,t.tms_cutime%60);
#endif
}

/* The trap command:

   trap <arg> <signal ...>
   trap <signal ...>
   trap -l
   trap

   Set things up so that ARG is executed when SIGNAL(s) N is recieved.
   If ARG is the empty string, then ignore the SIGNAL(s).  If there is
   no ARG, then set the trap for SIGNAL(s) to its original value.  Just
   plain "trap" means to print out the list of commands associated with
   each signal number.  Single arg of "-l" means list the signal names. */

/* Possible operations to perform on the list of signals.*/
#define SET 0			/* Set this signal to first_arg. */
#define REVERT 1		/* Revert to this signals original value. */
#define IGNORE 2		/* Ignore this signal. */

trap_builtin (list)
     WORD_LIST *list;
{
  register int i;

  if (!list) {
    for (i = 0; i < NSIG; i++)
      if ((int)(trap_list[i]) > 0)
	printf ("%d:%s: #(%s)\n", i, trap_list[i], signal_name (i));
  } else {
    char *first_arg = list->word->word;
    int operation = SET;

    if (strcmp ("-l", first_arg) == 0) {
      int column = 0;
      for (i = 0; i < NSIG; i++) {
	 printf ("%d) %s", i, signal_name (i));
	 if (++column < 4) printf ("\t");
	 else {
	   printf ("\n");
	   column = 0;
	 }
       }
      if (column != 0) printf ("\n");
      return;
    }

    if (signal_object_p (first_arg))
      operation = REVERT;
    else {
      list = list->next;
      if (*first_arg == '\0') operation = IGNORE;
    }

    while (list) {
      int signal = decode_signal (list->word->word);

      if (signal == NO_SIG)
	  report_error ("%s: not a signal specification", list->word->word);
      else {
	switch (operation) {

	case SET:
	  set_signal (signal, first_arg);
	  break;

	case REVERT:
	  restore_default_signal (signal);
	  break;

	case IGNORE:
	  ignore_signal (signal);
	  break;
	}
      }
      list = list->next;
    }
  }
}


/* For each word in LIST, find out what the shell is going to do with
   it as a simple command. i.e., which file would this shell use to
   execve, or if it is a builtin command, or an alias.  Possible flag
   arguments:
   	-type		Returns the "type" of the object, one of
			`alias', `function', `builtin', or `file'.

	-path		Returns the pathname of the file if -type is
			a file.

	-all		Returns all occurences of words, wether they
	 		be a filename in the path, alias, function,
			or builtin.
   Order of evaluation:
   	alias
	function
	builtin
	file
 */
type_builtin (list)
     WORD_LIST *list;
{
  extern Function *find_shell_builtin ();
  extern char *all_local_variables ();
  extern char *user_command_matches ();
  int path_only, type_only, all;
  int found_something = 0;
  int found_file = 0;

  path_only = type_only = all = 0;

  while (list && *(list->word->word) == '-')
    {
      char *flag = &(list->word->word[1]);

      if (strcmp (flag, "type") == 0)
	{
	  type_only = 1;
	  path_only = 0;
	}
      else if (strcmp (flag, "path") == 0 || strcmp (flag, "n") == 0)
	{
	  path_only = 1;
	  type_only = 0;
	}
      else if (strcmp (flag, "all") == 0)
	{
	  all = 1;
	}
      else
	{
	  report_error ("%s: bad flag `%s'", this_command_name, flag);
	  return (EXECUTION_FAILURE);
	}
      list = list->next;
    }

  while (list)
    {
      char *command = list->word->word;
      SHELL_VAR *var = find_variable (command);
      int hashed = 0;
      char *full_path = (char *)NULL;

#ifdef ALIAS
      /* Command is an alias? */
      ASSOC *alias = find_alias (command);
      if (alias)
	{
	  if (type_only)
	    printf ("alias\n");
	  else if (!path_only)
	    printf ("%s is aliased to `%s'\n", command, alias->value);
	  found_something++;
	  if (!all)
	    goto next_item;
	}
#endif

      /* Command is a function? */
      if (var && function_p (var))
	{
	  if (type_only)
	    printf ("function\n");
	  else if (!path_only)
	    {
	      printf ("%s is a function\n", command);
	      printf ("%s () ", command);
	      print_command (var->value);
	      printf ("\n");
	    }
	  found_something++;
	  if (!all)
	    goto next_item;
	}

      /* Command is a builtin? */
      if (find_shell_builtin (command))
	{
	  if (type_only)
	    printf ("builtin\n");
	  else if (!path_only)
	    printf ("%s is a shell builtin\n", command);
	  found_something++;
	  if (!all)
	    goto next_item;
	}

      full_path = NULL;

      /* Command is a disk file? */
      if (absolute_pathname (command))
	{
	  full_path = savestring (command);
	}
      else
	{
	  /* If the user isn't doing "-all", then we might care about
	     whether the file is present in our hash table. */
	  if (!all)
	    {
	      if ((full_path = find_hashed_filename (command)) != (char *)NULL)
		{
		  hashed++;
		  full_path = savestring (full_path);
		}
	      else
		{
		  full_path = (char *)find_user_command (command);
		}
	    }
	}
      if (all)
	{
	  /* If full_path was set then it is an absolute path name. */

	  if (full_path)
	    {
	      found_something++;
	      if (type_only)
		printf ("file\n");
	      else if (path_only)
		printf ("%s\n", full_path);
	      else
		printf ("%s is %s\n", command, full_path);
	      
	      free (full_path);
	    }
	  else
	    while (full_path = 
		   user_command_matches (command, 1, found_file))
	      {
		found_something++;
		found_file++;
		
		if (type_only)
		  printf ("file\n");
		else if (path_only)
		  printf ("%s\n", full_path);
		else
		  printf ("%s is %s\n", command, full_path);
		
		free (full_path);
	      }
	  
	  if (!found_something)
	    printf ("%s not found\n", command);

	  goto next_item;
	}

      /* We can only get here if "-all" was not specified. */
      if (!full_path)
	{
	  if (!type_only && !path_only)
	    printf ("%s not found\n", command);
	}
      else
	{
	  if (type_only)
	    printf ("file\n");
	  else if (path_only)
	    printf ("%s\n", full_path);
	  else
	    {
	      if (hashed)
		printf ("%s is hashed (%s)\n", command, full_path);
	      else
		printf ("%s is %s\n", command, full_path);
	    }

	  free (full_path);
	}
    next_item:
      list = list->next;
    }
}

/* Declare or change variable attributes. */
declare_builtin (list)
     register WORD_LIST *list;
{
  return (declare_internal (list, 0));
}

local_builtin (list)
     register WORD_LIST *list;
{
  extern int variable_context;

  if (variable_context)
    return (declare_internal (list, 1));
  else
    {
      report_error ("%s: can only be used in a function", this_command_name);
      return (EXECUTION_FAILURE);
    }
}

declare_internal (list, no_modifiers)
     register WORD_LIST *list;
     int no_modifiers;
{
  extern int variable_context;
  int flags_on = 0, flags_off = 0;

  while (list)
    {
      register char *t = list->word->word;
      int *flags;

      if (*t != '+' && *t != '-')
	break;

      if (no_modifiers)
	{
	  report_error ("%s: modifiers not allowed", this_command_name);
	  return (EXECUTION_FAILURE);
	}

      if (*t == '+')
	flags = &flags_off;
      else
	flags = &flags_on;

      t++;

      while (*t)
	{
	  if (*t == 'f')
	    *flags |= att_function, t++;
	  else if (*t == 'x')
	    *flags |= att_exported, t++, array_needs_making = 1;
	  else if (*t == 'r')
	    *flags |= att_readonly, t++;
	  else
	    {
	      report_error ("%s: Bad flag `-%c'", this_command_name, *t);
	      return (EXECUTION_FAILURE);
	    }
	}

      list = list->next;
    }

  /* If there are no more arguments left, then we just want to show
     some variables. */
  if (!list)
    {
      /* If we didn't allow modifiers, then this is the `local' command.
	 Perhaps the flag should be called `local_command' instead of
	 `no_modifiers'.  At any rate, only show local variables defined
	 at this context level. */
      if (no_modifiers)
	{
	  extern SHELL_VAR *variable_list;
	  register SHELL_VAR *vlist = variable_list;

	  while (vlist)
	    {
	      if (!invisible_p (vlist) && vlist->context == variable_context)
		{
		  print_assignment (vlist);
		  printf ("\n");
		}
	      vlist = vlist->next;
	    }
	}
      else
	{
	  if (!flags_on)
	    set_builtin ((WORD_LIST *)NULL);
	  else
	    set_or_show_attributes ((WORD_LIST *)NULL, flags_on);
	}

      return (EXECUTION_SUCCESS);
    }

  /* There are arguments left, so we are making variables. */
  while (list)
    {
      char *value, *name = savestring (list->word->word);
      int offset = assignment (name);

      if (offset)
	{
	  name[offset] = '\0';
	  value = name + offset + 1;
	}
      else
	{
	  value = "";
	}

      /* If VARIABLE_CONTEXT has a non-zero value, then we are executing
	 inside of a function.  This means we should make local variables,
	 not global ones. */

      if (variable_context)
	make_local_variable (name);

      /* If we are declaring a function, then complain about it in some way.
	 We don't let people make functions by saying `typeset -f foo=bar'. */

      if (flags_on & att_function)
	{
	  report_error ("%s: can't use `-f' to make functions\n",
			this_command_name);
	}
      else
	{
	  SHELL_VAR *bind_variable (), *find_variable (), *v;

	  if ((v = find_variable (name)) == (SHELL_VAR *)NULL)
	    v = bind_variable (name, "");

	  /* We are not allowed to rebind readonly variables that
	     already are readonly unless we are turning off the
	     readonly bit. */
	  if (flags_off & att_readonly)
	    flags_on &= ~att_readonly;

	  if (value && readonly_p (v) && (!(flags_off & att_readonly)))
	    {
	      report_error ("%s: %s: readonly variable",
			    this_command_name, name);
	      goto hack_next_variable;
	    }
	    
	  v->attributes |= flags_on;

	  if (function_p (v) && (flags_off & att_function))
	    {
	      dispose_command ((COMMAND *)v->value);
	      v->value = "";
	    }
	  v->attributes &= ~flags_off;

	  if (offset)
	    {
	      free (v->value);
	      v->value = savestring (value);
	    }
	}

      stupidly_hack_special_variables (name);

    hack_next_variable:
      free (name);
      list = list->next;
    }
  return (EXECUTION_SUCCESS);
}

/* Set or display the mask used by the system when creating files. */
umask_builtin (list)
     WORD_LIST *list;
{
  if (list) {
    int new_umask;
    new_umask = read_octal (list->word->word);

    /* Note that other shells just let you set the umask to zero
       by specifying a number out of range.  This is a problem
       with those shells.  We don't change the umask if the input
       is lousy. */
    if (new_umask == -1) {
      report_error ("umask: `%s' is not an octal number from 000 to 777", list->word->word);
      return (EXECUTION_FAILURE);
    }
    umask (new_umask);
  } else {
    /* Display the UMASK for this user. */
    int old_umask = umask (022);
    umask (old_umask);
    printf ("%03o\n", old_umask);
  }
}

/* Return the octal number parsed from STRING, or -1 to indicate
   that the string contained a bad number. */
read_octal (string)
     char *string;
{
  int result = 0;
  int digits = 0;
  while (*string && *string >= '0' && *string < '8') {
    digits++;
    result = (result * 8) + *string++ - '0';
  }
  if (!digits || result > 0777 || *string) return (-1);
  return (result);
}

/* Remove the variables specified in LIST from VARIABLE_LIST. */
unset_builtin (list)
  WORD_LIST *list;
{
  extern char **non_unsettable_vars;
  int result = 0;

  while (list)
    {
      char *name = list->word->word;
      if (find_name_in_list (name, non_unsettable_vars) > -1)
	report_error ("%s: cannot unset", name);
      else
	{
	  if (makunbound (list->word->word) == -1)
	    result++;
	  else
	    stupidly_hack_special_variables (list->word->word);
	}
      list = list->next;
    }
  return (result);
}

/* Wait for the pid in LIST to stop or die. */
wait_builtin (list)
     WORD_LIST *list;
{
  extern wait_for_background_pids ();
  extern wait_for_single_pid ();

  /* We support jobs or pids.
     wait <pid-or-job> [pid-or-job ...] */

  /* But wait without any arguments means to wait for all of the shell's
     currently active background processes. */
  if (!list)
    {
      wait_for_background_pids ();
      return (EXECUTION_SUCCESS);
    }

  while (list)
    {
      int pid;
      if (digit (*(list->word->word)))
	{
	  if (sscanf (list->word->word, "%d", &pid) == 1)
	    {
	      wait_for_single_pid (pid);
	      return (EXECUTION_SUCCESS);
	    }
	  else
	    {
	      report_error ("wait: %s not a pid or job spec",
			    list->word->word);
	      return (EXECUTION_FAILURE);
	    }
	}
      else
#ifdef JOB_CONTROL
	/* Must be a job spec.  Check it out. */
	{
	  int oldmask = sigblock (sigmask (SIGCHLD));
	  int job = get_job_spec (list);

	  if (job < 0 || job >= job_slots || !jobs[job])
	    {
	      if (job != DUP_JOB)
		report_error ("No such job %s", list->word->word);
	      sigsetmask (oldmask);
	      return (EXECUTION_FAILURE);
	    }

	  /* Job spec used.  Wait for the parent pid. */
	  pid = jobs[job]->pgrp;

	  sigsetmask (oldmask);

	  wait_for_single_pid (pid);
	}
#endif /* JOB_CONTROL */
      list = list->next;
    }
  return (EXECUTION_SUCCESS);
}

/* Run the command mentioned in list directly, without going through the
   normal alias/function/builtin/filename lookup process. */
builtin_builtin (list)
     WORD_LIST *list;
{
  extern Function *find_shell_builtin ();
  Function *function;
  register char *command;

  if (!list)
    return (EXECUTION_SUCCESS);

  command = (list->word->word);
  function = find_shell_builtin (command);

  if (!function)
    {
      report_error ("%s: Not a shell builtin!", command);
      return (EXECUTION_FAILURE);
    }
  else
    {
      this_command_name = command;
      list = list->next;
      return ((*function) (list));
    }
}

char tdir[MAXPATHLEN];
/* Return a pretty pathname.  If the first part of the pathname is
   the same as $HOME, then replace that with `~'.  */
char *
polite_directory_format (name)
     char *name;
{
  char *home = get_string_value ("HOME");
  int l = home ? strlen (home) : 0;

  if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/'))
    {
      strcpy (tdir + 1, name + l);
      tdir[0] = '~';
      return (tdir);
    }
  else
    return (name);
}

#ifdef PUSHD_AND_POPD

/* Some useful commands whose behaviour has been observed in csh. */

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

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

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

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

  /* If there is no argument list then switch current and
     top of list. */
  if (!list)
    {
      if (!directory_list_offset)
	{
	  report_error ("pushd: 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
    {
      if (*(list->word->word) == '+')
	{
	  int num;
	  if (1 == sscanf (&(list->word->word)[1], "%d", &num))
	    {
	      if (num > directory_list_offset)
		{
		  if (!directory_list_offset)
		    report_error ("pushd: directory stack empty");
		  else
		    report_error ("pushd: directory stack isn't %d deep", num);
		  return (EXECUTION_FAILURE);
		}
	      else
		{
		  /* Rotate the stack num times.  Remember, the
		     current directory 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)
	    {
	      if (!pushd_directory_list)
		pushd_directory_list =
		  (char **)xmalloc ((directory_list_size = 10) * sizeof (char *));
	      else
		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);
	}
    }
}

/* Print the current list of directories on the directory stack. */
dirs_builtin (list)
     WORD_LIST *list;
{
  register int i, format = 0;
  char *temp, *polite_directory_format (), *get_working_directory ();

  /* Maybe do long form? */
  if (list)
    {
      if (strcmp (list->word->word, "-l") == 0)
	format++;

      if (!format || list->next)
	{
	  report_error ("usage: dirs [ -l ]");
	  return (EXECUTION_FAILURE);
	}
    }

  /* The first directory printed is always the current working directory. */
  temp = get_working_directory ("dirs");
  if (!temp)
    temp = savestring ("<no directory>");
  printf ("%s ", format ? temp : polite_directory_format (temp));
  free (temp);

  /* Now print any directories in the array. */
  for (i = (directory_list_offset - 1); i > -1; i--)
    printf ("%s ", format ? pushd_directory_list[i] :
	    polite_directory_format (pushd_directory_list[i]));

  printf ("\n");
  return (EXECUTION_SUCCESS);
}

/* 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. */
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);
}

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

  if (list)
    {
      if ((*(list->word->word) != '+') ||
	  (1 != sscanf (&((list->word->word)[1]), "%d", &which)))
	{
	  report_error ("popd: bad arg `%s'", list->word->word);
	  return (EXECUTION_FAILURE);
	}
    }
  if (which > directory_list_offset || (!directory_list_offset && !which))
    {
      if (!directory_list_offset)
	report_error ("popd: directory stack empty");
      else
	report_error ("popd: stack is only %d deep", directory_list_offset);
      return (EXECUTION_FAILURE);
    }

  if (!which)
    {
      i = cd_to_string (pushd_directory_list[directory_list_offset - 1]);
      if (i != EXECUTION_SUCCESS)
	return (i);
      free (pushd_directory_list[--directory_list_offset]);
      goto exit_and_win;
    }

  /* Since an offset was specified, remove that directory from the list.
     There is nothing else left to do, since the top directory hasn't
     changed. */
  free (pushd_directory_list[directory_list_offset - which]);

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

  directory_list_offset--;

 exit_and_win:
  dirs_builtin ((WORD_LIST *)NULL);
  return (EXECUTION_SUCCESS);
}

#endif  /* PUSHD_AND_POPD */

#ifdef ALIAS

/* Hack the alias command in a Korn shell way. */
alias_builtin (list)
     WORD_LIST *list;
{
  if (!list)
    {
      register int i;

      if (!aliases)
	return (EXECUTION_FAILURE);

      for (i = 0; aliases[i]; i++)
	print_alias (aliases[i]);
    }
  else
    {
      while (list)
	{
	  register char *value, *name = list->word->word;
	  register int offset;

	  for (offset = 0; name[offset] && name[offset] != '='; offset++);

	  if (offset && name[offset] == '=')
	    {
	      name[offset] = '\0';
	      value = name + offset + 1;

	      add_alias (name, value);
	    }
	  else
	    {
	      ASSOC *t = find_alias (name);
	      if (t)
		print_alias (t);
	    }
	  list = list->next;
	}
    }
  return (EXECUTION_SUCCESS);
}

/* Remove aliases named in LIST from the aliases database. */
unalias_builtin (list)
     register WORD_LIST *list;
{
  register ASSOC *alias;
  int result = 0;

  while (list) {

    alias = find_alias (list->word->word);

    if (alias)
      remove_alias (alias->name);
    else
      result++;

    list = list->next;
  }
  return (result);
}

/* Output ALIAS in such a way as to allow it to be read back in. */
print_alias (alias)
     ASSOC *alias;
{
  register int i;

  for (i = 0; alias->value[i] && !whitespace (alias->value[i]); i++);

  if (alias->value[i])
    printf ("alias %s=\"%s\"\n", alias->name, alias->value);
  else
    printf ("alias %s=%s\n", alias->name, alias->value);
}

#endif  /* ALIAS */


#ifdef JOB_CONTROL
/* **************************************************************** */
/*								    */
/*			Job Control!				    */
/*								    */
/* **************************************************************** */


/* The `jobs' command.  Prints outs a list of active jobs.  If the
   first argument is `-l', then the process id's are printed also. */
jobs_builtin (list)
     WORD_LIST *list;
{
  int long_form = 0;

  if (list && (strcmp (list->word->word, "-l") == 0))
    long_form = 1;

  list_jobs (long_form);
}

/* Suspend the current shell.  Not hard to do. */

static SigHandler *old_cont, *old_tstp;

/* Continue handler. */
sighandler
suspend_continue ()
{
  signal (SIGCONT, old_cont);
  signal (SIGTSTP, old_tstp);
}

/* Suspending the shell.  If -f is the arg, then do the suspend
   no matter what.  Otherwise, complain if a login shell. */
suspend_builtin (list)
     WORD_LIST *list;
{

  if (list)
    if (strcmp (list->word->word, "-f") == 0)
      goto do_suspend;

  no_args ("suspend", list);

  if (login_shell) {
    report_error ("Can't suspend a login shell");
    longjmp (top_level, DISCARD);
  }
do_suspend:
  old_cont = (SigHandler *)signal (SIGCONT, suspend_continue);
  old_tstp = (SigHandler *)signal (SIGTSTP, SIG_DFL);
  kill (getpid (), SIGTSTP);
}

/* How to bring a job into the foreground. */
fg_builtin (list)
     WORD_LIST *list;
{
  int fg_bit = 1;

  /* If the last arg on the line is '&', then start this job in the
     background.  Else, fg the job. */
  {
    register WORD_LIST *t = list;

      while (t && t->next);
    if (t && strcmp (t->word->word, "&") == 0)
      fg_bit = 0;
  }

  fg_bg (list, fg_bit);
}

/* How to put a job into the background. */
bg_builtin (list)
     WORD_LIST *list;
{
  fg_bg (list, 0);
}

/* How to put a job into the foreground/background. */
fg_bg (list, foreground)
     WORD_LIST *list;
     int foreground;
{
  int job = get_job_spec (list);

  if (job < 0 || job >= job_slots || !jobs[job])
    {
      if (job != DUP_JOB)
	report_error ("No such job %s", list ? list->word->word : "");
      longjmp (top_level, DISCARD);
    }

  if (start_job (job, foreground))
    return (EXECUTION_SUCCESS);
  else
    return (EXECUTION_FAILURE);
}

/* Return the job spec found in LIST. */
get_job_spec (list)
     WORD_LIST *list;
{
  register char *word;
  int job = NO_JOB;
  int substring = 0;

  if (!list)
    return (current_job);

  word = list->word->word;

  if (!*word)
    return (current_job);

  if (*word == '%')
    word++;

  if (digit (*word) && (sscanf (word, "%d", &job) == 1))
    return (job - 1);

  switch (*word) {

  case 0:
  case '%':
  case '+':
    return (current_job);

  case '-':
    return (previous_job);

  case '?':			/* Substring search requested. */
    substring++;
    word++;
    goto find_string;

  default:
  find_string:
    {
      register int i, wl = strlen (word);
      for (i = 0; i < job_slots; i++)
	{
	  if (jobs[i])
	    {
	      register PROCESS *p = jobs[i]->pipe;
	      extern char *strindex ();
	      do
		{
		  if ((substring && strindex (p->command, word)) ||
		      (strncmp (p->command, word, wl) == 0))
		    if (job != NO_JOB)
		      {
			report_error ("Ambigious job spec: %s", word);
			return (DUP_JOB);
		      }
		    else
		      job = i;

		  p = p->next;
		}
	      while (p != jobs[i]->pipe);
	    }
	}
      return (job);
    }
  }
}

#ifndef CONTINUE_AFTER_KILL_ERROR
#define CONTINUE_OR_FAIL return (EXECUTION_FAILURE)
#else
#define CONTINUE_OR_FAIL goto continue_killing
#endif

/* Here is the kill builtin.  We only have it so that people can type
   kill -KILL %1?  No, if you fill up the process table this way you
   can kill some. */
kill_builtin (list)
     WORD_LIST *list;
{
  int signal = SIGTERM;
  int pid;

  if (!list)
    return (EXECUTION_SUCCESS);

  if (strcmp (list->word->word, "-l") == 0)
    {
      register int i, column = 0;
      for (i = 1; i < NSIG; i++)
	{
	  printf ("%d) %s", i, signal_name (i));
	  if (++column < 4)
	    printf ("\t");
	  else {
	    printf ("\n");
	    column = 0;
	  }
	}
      if (column != 0)
	printf ("\n");
      return (EXECUTION_SUCCESS);
    }

  /* If the user specified a signal, use that. */
  if (*(list->word->word) == '-')
    {
      signal = decode_signal (&(list->word->word)[1]);
      if (signal == NO_SIG)
	{
	  report_error ("kill: bad signal spec `%s'", &(list->word->word)[1]);
	  return (EXECUTION_FAILURE);
	}
      else
	{
	  list = list->next;
	}
    }

  while (list)
    {
      if (digit (*(list->word->word)))
	{
	  if (sscanf (list->word->word, "%d", &pid) == 1)
	    {
	      if (kill_pid (pid, signal, 0) < 0)
		goto signal_error;
	    }
	  else
	    {
	      report_error ("kill: No such pid %d", pid);
	      CONTINUE_OR_FAIL;
	    }
	}
      else
	{			/* Must be a job spec.  Check it out. */
	  int oldmask = sigblock (sigmask (SIGCHLD));
	  int job = get_job_spec (list);

	  if (job < 0 || job >= job_slots || !jobs[job])
	    {
	      if (job != DUP_JOB)
		report_error ("No such job %s", list->word->word);
	      sigsetmask (oldmask);
	      CONTINUE_OR_FAIL;
	    }

	  /* Job spec used.  Kill the process group. */
	  pid = jobs[job]->pgrp;

	  sigsetmask (oldmask);

	  if (kill_pid (pid, signal, 1) < 0)
	    {
	    signal_error:
	      if (errno == EPERM)
		report_error ("kill: (%d) - Not owner", pid);
	      else if (errno == ESRCH)
		report_error ("kill: (%d) - No such pid", pid);
	      else
		report_error ("kill: Invalid signal %d", signal);
	      CONTINUE_OR_FAIL;
	    }
	}
    continue_killing:
      list = list->next;
    }
  return (EXECUTION_SUCCESS);
}

#endif  /* JOB_CONTROL */