diff --git a/job.c b/job.c
new file mode 100644
index 00000000..50192024
--- /dev/null
+++ b/job.c
@@ -0,0 +1,1461 @@
+/* Job execution and handling for GNU Make.
+Copyright (C) 1988, 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Make 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 GNU Make; see the file COPYING.  If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include "make.h"
+#include "commands.h"
+#include "job.h"
+#include "file.h"
+#include "variable.h"
+#include <errno.h>
+
+/* Default path to search for executables.  */
+static char default_path[] = ":/bin:/usr/bin";
+
+/* Default shell to use.  */
+char default_shell[] = "/bin/sh";
+
+extern int errno;
+
+#if	defined(POSIX) || defined(__GNU_LIBRARY__)
+#include <limits.h>
+#include <unistd.h>
+#define	GET_NGROUPS_MAX	sysconf (_SC_NGROUPS_MAX)
+#else	/* Not POSIX.  */
+#ifndef	USG
+#include <sys/param.h>
+#define	NGROUPS_MAX	NGROUPS
+#endif	/* Not USG.  */
+#endif	/* POSIX.  */
+
+#ifdef	POSIX
+#include <sys/wait.h>
+
+#define	WAIT_NOHANG(status)	waitpid(-1, (status), WNOHANG)
+
+#else	/* Not POSIX.  */
+
+#if	defined(HAVE_SYS_WAIT) || !defined(USG)
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#ifndef	wait3
+extern int wait3 ();
+#endif
+#define	WAIT_NOHANG(status) \
+  wait3((union wait *) (status), WNOHANG, (struct rusage *) 0)
+
+#if	!defined (wait) && !defined (POSIX)
+extern int wait ();
+#endif
+#endif	/* HAVE_SYS_WAIT || !USG */
+#endif	/* POSIX.  */
+
+#if	defined(WTERMSIG) || (defined(USG) && !defined(HAVE_SYS_WAIT))
+#define	WAIT_T int
+
+#ifndef	WTERMSIG
+#define WTERMSIG(x) ((x) & 0x7f)
+#endif
+#ifndef	WCOREDUMP
+#define WCOREDUMP(x) ((x) & 0x80)
+#endif
+#ifndef	WEXITSTATUS
+#define WEXITSTATUS(x) (((x) >> 8) & 0xff)
+#endif
+#ifndef	WIFSIGNALED
+#define WIFSIGNALED(x) (WTERMSIG (x) != 0)
+#endif
+#ifndef	WIFEXITED
+#define WIFEXITED(x) (WTERMSIG (x) == 0)
+#endif
+
+#else	/* WTERMSIG not defined and have <sys/wait.h> or not USG.  */
+
+#define WAIT_T union wait
+#define WTERMSIG(x)	((x).w_termsig)
+#define WCOREDUMP(x)	((x).w_coredump)
+#define WEXITSTATUS(x)	((x).w_retcode)
+#ifndef	WIFSIGNALED
+#define	WIFSIGNALED(x)	(WTERMSIG(x) != 0)
+#endif
+#ifndef	WIFEXITED
+#define	WIFEXITED(x)	(WTERMSIG(x) == 0)
+#endif
+
+#endif	/* WTERMSIG defined or USG and don't have <sys/wait.h>.  */
+
+
+#if	defined(__GNU_LIBRARY__) || defined(POSIX)
+
+#include <sys/types.h>
+#define	GID_T	gid_t
+
+#else	/* Not GNU C library.  */
+
+#define	GID_T	int
+
+extern int dup2 ();
+extern int execve ();
+extern void _exit ();
+extern int geteuid (), getegid ();
+extern int setgid (), getgid ();
+#endif	/* GNU C library.  */
+
+#ifndef USG
+extern int getdtablesize ();
+#else
+#include <sys/param.h>
+#define getdtablesize() NOFILE
+#endif
+
+extern void wait_to_start_job ();
+extern int start_remote_job_p ();
+extern int start_remote_job (), remote_status ();
+
+
+#if	(defined(USG) && !defined(HAVE_SIGLIST)) || defined(DGUX)
+static char *sys_siglist[NSIG];
+void init_siglist ();
+#else	/* Not (USG and HAVE_SIGLIST), or DGUX.  */
+extern char *sys_siglist[];
+#endif	/* USG and not HAVE_SIGLIST, or DGUX.  */
+
+int child_handler ();
+static void free_child (), start_job ();
+
+/* Chain of all children.  */
+
+struct child *children = 0;
+
+/* Number of children currently running.  */
+
+unsigned int job_slots_used = 0;
+
+/* Nonzero if the `good' standard input is in use.  */
+
+static int good_stdin_used = 0;
+
+/* Write an error message describing the exit status given in
+   EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
+   Append "(ignored)" if IGNORED is nonzero.  */
+
+static void
+child_error (target_name, exit_code, exit_sig, coredump, ignored)
+     char *target_name;
+     int exit_code, exit_sig, coredump;
+     int ignored;
+{
+  char *ignore_string = ignored ? " (ignored)" : "";
+
+  if (exit_sig == 0)
+    error ("*** [%s] Error %d%s", target_name, exit_code, ignore_string);
+  else
+    {
+      char *coredump_string = coredump ? " (core dumped)" : "";
+      if (exit_sig > 0 && exit_sig < NSIG)
+	error ("*** [%s] %s%s",
+	       target_name, sys_siglist[exit_sig], coredump_string);
+      else
+	error ("*** [%s] Signal %d%s", target_name, exit_sig, coredump_string);
+    }
+}
+
+extern void block_remote_children (), unblock_remote_children ();
+
+extern int fatal_signal_mask;
+
+#ifdef	USG
+/* Set nonzero in the interval when it's possible that we may see a dead
+   child that's not in the `children' chain.  */
+static int unknown_children_possible = 0;
+#endif
+
+
+/* Block the child termination signal and fatal signals.  */
+
+static void
+block_signals ()
+{
+#ifdef USG
+
+  /* Tell child_handler that it might see children that aren't yet
+     in the `children' chain.  */
+  unknown_children_possible = 1;
+
+  /* Ignoring SIGCLD makes wait always return -1.
+     Using the default action does the right thing.  */
+  (void) SIGNAL (SIGCLD, SIG_DFL);
+
+#else	/* Not USG.  */
+
+  /* Block the signals.  */
+  (void) sigblock (fatal_signal_mask | sigmask (SIGCHLD));
+
+#endif
+
+  block_remote_children ();
+}
+
+/* Unblock the child termination signal and fatal signals.  */
+static void
+unblock_signals ()
+{
+#ifdef	USG
+
+  (void) SIGNAL (SIGCLD, child_handler);
+
+  /* It should no longer be possible for children not in the chain to die.  */
+  unknown_children_possible = 0;
+
+#else	/* Not USG.  */
+
+  /* Unblock the signals.  */
+  (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask | sigmask (SIGCHLD)));
+
+#endif
+
+  unblock_remote_children ();
+}
+
+static char *signals_blocked_p_stack = 0;
+static unsigned int signals_blocked_p_max;
+static unsigned int signals_blocked_p_depth;
+
+/* Make signals blocked in FLAG is nonzero, unblocked if FLAG is zero.
+   Push this setting on the signals_blocked_p_stack, so it can be
+   popped off by pop_signals_blocked_p.  */
+
+void
+push_signals_blocked_p (flag)
+     int flag;
+{
+  int blocked;
+
+  if (signals_blocked_p_stack == 0)
+    {
+      signals_blocked_p_max = 8;
+      signals_blocked_p_stack = (char *) xmalloc (8);
+      signals_blocked_p_depth = 1;
+      signals_blocked_p_stack[0] = flag;
+
+      blocked = 0;
+    }
+  else
+    {
+      if (signals_blocked_p_depth == signals_blocked_p_max)
+	{
+	  signals_blocked_p_max += 8;
+	  signals_blocked_p_stack
+	    = (char *) xrealloc(signals_blocked_p_stack,
+				signals_blocked_p_max);
+	}
+
+      blocked = (signals_blocked_p_depth > 0
+		 && signals_blocked_p_stack[signals_blocked_p_depth - 1]);
+
+      signals_blocked_p_stack[++signals_blocked_p_depth - 1] = flag;
+    }
+
+  if (blocked && !flag)
+    unblock_signals ();
+  else if (flag && !blocked)
+    block_signals ();
+}
+
+/* Pop the signals_blocked_p setting from the stack
+   and block or unblock signals as appropriate.  */
+
+void
+pop_signals_blocked_p ()
+{
+  int blocked, block;
+
+  blocked = (signals_blocked_p_depth > 0
+	     && signals_blocked_p_stack[signals_blocked_p_depth-- - 1]);
+
+  block = (signals_blocked_p_depth > 0
+	   && signals_blocked_p_stack[signals_blocked_p_depth - 1]);
+
+  if (block && !blocked)
+    block_signals ();
+  else if (blocked && !block)
+    unblock_signals ();
+}
+
+extern int shell_function_pid, shell_function_completed;
+
+/* Handle a child-termination signal (SIGCHLD, or SIGCLD for USG),
+   storing the returned status and the new command state (`cs_finished')
+   in the `file' member of the `struct child' for the dead child,
+   and removing the child from the chain.
+
+   If we were called as a signal handler, SIG should be SIGCHLD
+   (SIGCLD for USG).  If instead it is zero, we were called explicitly
+   and should block waiting for running children.
+   If SIG is < 0, - SIG is the maximum number of children to bury (record
+   status of and remove from the chain).  */
+
+int
+child_handler (sig)
+     int sig;
+{
+  WAIT_T status;
+  unsigned int dead_children = 0;
+
+  if (sig > 0)
+    block_signals ();
+
+  while (1)
+    {
+      int remote = 0;
+      register int pid;
+      int exit_code, exit_sig, coredump;
+      register struct child *lastc, *c;
+      int child_failed;
+
+      /* First, check for remote children.  */
+      pid = remote_status (&exit_code, &exit_sig, &coredump, 0);
+      if (pid < 0)
+	{
+	  /* No remote children.  Check for local children.  */
+
+#ifdef	WAIT_NOHANG
+	  if (sig > 0)
+	    pid = WAIT_NOHANG (&status);
+	  else
+	    pid = wait (&status);
+#else	/* USG and don't HAVE_SYS_WAIT.  */
+	  /* System V cannot do non-blocking waits, so we have two
+	     choices if called as a signal handler: handle only one
+	     child (there may be more if the signal was blocked),
+	     or block waiting for more.  The latter option makes
+	     parallelism useless, so we must choose the former.  */
+	  pid = wait (&status);
+#endif	/* HAVE_SYS_WAIT or not USG.  */
+
+	  if (pid <= 0)
+	    /* No local children.  */
+	    break;
+	  else
+	    {
+	      /* Chop the status word up.  */
+	      exit_code = WEXITSTATUS (status);
+	      exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
+	      coredump = WCOREDUMP (status);
+	    }
+	}
+      else
+	/* We got a remote child.  */
+	remote = 1;
+
+      /* Check if this is the child of the `shell' function.  */
+      if (!remote && pid == shell_function_pid)
+	{
+	  /* It is.  Leave an indicator for the `shell' function.  */
+	  if (exit_sig == 0 && exit_code == 127)
+	    shell_function_completed = -1;
+	  else
+	    shell_function_completed = 1;
+
+	  /* Check if we have reached our quota of children.  */
+	  ++dead_children;
+	  if (sig < 0 && dead_children == -sig)
+	    break;
+#if	defined(USG) && !defined(HAVE_SYS_WAIT)
+	  else if (sig > 0)
+	    break;
+#endif
+	  else
+	    continue;
+	}
+
+      child_failed = exit_sig != 0 || exit_code != 0;
+
+      /* Search for a child matching the deceased one.  */
+      lastc = 0;
+      for (c = children; c != 0; lastc = c, c = c->next)
+	if (c->remote == remote && c->pid == pid)
+	  break;
+
+      if (c == 0)
+	{
+	  /* An unknown child died.  */
+#ifdef	USG
+	  if (!unknown_children_possible)
+	    {
+#endif
+	      char buf[100];
+	      sprintf (buf, "Unknown%s job %d", remote ? " remote" : "", pid);
+	      if (child_failed)
+		child_error (buf, exit_code, exit_sig, coredump,
+			     ignore_errors_flag);
+	      else
+		error ("%s finished.", buf);
+#ifdef	USG
+	    }
+#endif
+	}
+      else
+	{
+	  /* If this child had the good stdin, say it is now free.  */
+	  if (c->good_stdin)
+	    good_stdin_used = 0;
+
+	  if (child_failed && !c->noerror && !ignore_errors_flag)
+	    {
+	      /* The commands failed.  Write an error message,
+		 delete non-precious targets, and abort.  */
+	      child_error (c->file->name, exit_code, exit_sig, coredump, 0);
+	      c->file->update_status = 1;
+	      if (exit_sig != 0)
+		delete_child_targets (c);
+	    }
+	  else
+	    {
+	      if (child_failed)
+		{
+		  /* The commands failed, but we don't care.  */
+		  child_error (c->file->name,
+			       exit_code, exit_sig, coredump, 1);
+		  child_failed = 0;
+		}
+
+	      /* If there are more commands to run, try to start them.  */
+	      start_job (c);
+
+	      switch (c->file->command_state)
+		{
+		case cs_running:
+		  /* Successfully started.  Loop to reap more children.  */
+		  continue;
+
+		case cs_finished:
+		  if (c->file->update_status != 0)
+		    {
+		      /* We failed to start the commands.  */
+		      delete_child_targets (c);
+		    }
+		  break;
+
+		default:
+		  error ("internal error: `%s' command_state \
+%d in child_handler", c->file->name);
+		  abort ();
+		  break;
+		}
+	    }
+
+	  /* Set the state flag to say the commands have finished.  */
+	  notice_finished_file (c->file);
+
+	  /* Remove the child from the chain and free it.  */
+	  if (lastc == 0)
+	    children = c->next;
+	  else
+	    lastc->next = c->next;
+	  free_child (c);
+
+	  /* There is now another slot open.  */
+	  --job_slots_used;
+
+	  /* If the job failed, and the -k flag was not given, die.  */
+	  if (child_failed && !keep_going_flag)
+	    die (1);
+
+	  /* See if we have reached our quota for blocking.  */
+	  ++dead_children;
+	  if (sig < 0 && dead_children == -sig)
+	    break;
+#if	defined(USG) && !defined(HAVE_SYS_WAIT)
+	  else if (sig > 0)
+	    break;
+#endif
+	}
+    }
+
+#ifdef	USG
+  if (sig > 0)
+    (void) SIGNAL (sig, child_handler);
+#endif
+
+  if (sig > 0)
+    unblock_signals ();
+
+  return 0;
+}
+
+
+/* Wait for N children, blocking if necessary.
+   If N is zero, wait until we run out of children.
+   If ERR is nonzero and we have any children to wait for,
+   print a message on stderr.  */
+
+void
+wait_for_children (n, err)
+     unsigned int n;
+     int err;
+{
+  push_signals_blocked_p (1);
+
+  if (err && (children != 0 || shell_function_pid != 0))
+    {
+      fflush (stdout);
+      error ("*** Waiting for unfinished jobs....");
+    }
+
+  /* Call child_handler to do the work.  */
+  (void) child_handler (- (int) n);
+
+  pop_signals_blocked_p ();
+}
+
+/* Free the storage allocated for CHILD.  */
+
+static void
+free_child (child)
+     register struct child *child;
+{
+  if (child->command_lines != 0)
+    {
+      register unsigned int i;
+      for (i = 0; i < child->file->cmds->ncommand_lines; ++i)
+	free (child->command_lines[i]);
+      free ((char *) child->command_lines);
+    }
+
+  if (child->environment != 0)
+    {
+      register char **ep = child->environment;
+      while (*ep != 0)
+	free (*ep++);
+      free ((char *) child->environment);
+    }
+
+  free ((char *) child);
+}
+
+/* Start a job to run the commands specified in CHILD.
+   CHILD is updated to reflect the commands and ID of the child process.  */
+
+static void
+start_job (child)
+     register struct child *child;
+{
+  static int bad_stdin = -1;
+  register char *p;
+  char noprint = 0, recursive;
+  char **argv;
+
+  if (child->command_ptr == 0 || *child->command_ptr == '\0')
+    {
+      /* There are no more lines in the expansion of this line.  */
+      if (child->command_line == child->file->cmds->ncommand_lines)
+	{
+	  /* There are no more lines to be expanded.  */
+	  child->command_ptr = 0;
+	  child->file->command_state = cs_finished;
+	  child->file->update_status = 0;
+	  return;
+	}
+      else
+	{
+	  /* Get the next line to run, and set RECURSIVE
+	     if the unexpanded line contains $(MAKE).  */
+	  child->command_ptr = child->command_lines[child->command_line];
+	  recursive = child->file->cmds->lines_recurse[child->command_line];
+	  ++child->command_line;
+	}
+    }
+  else
+    /* Still executing the last line we started.  */
+    recursive = child->file->cmds->lines_recurse[child->command_line - 1];
+
+  p = child->command_ptr;
+  child->noerror = 0;
+  while (*p != '\0')
+    {
+      if (*p == '@')
+	noprint = 1;
+      else if (*p == '-')
+	child->noerror = 1;
+      else if (*p == '+')
+	recursive = 1;
+      else if (!isblank (*p))
+	break;
+      ++p;
+    }
+
+  /* If -q was given, just say that updating `failed'.  */
+  if (question_flag && !recursive)
+    goto error;
+
+  /* There may be some preceding whitespace left if there
+     was nothing but a backslash on the first line.  */
+  p = next_token (p);
+
+  /* Figure out an argument list from this command line.  */
+
+  {
+    char *end;
+    argv = construct_command_argv (p, &end, child->file);
+    if (end == NULL)
+      child->command_ptr = NULL;
+    else
+      {
+	*end++ = '\0';
+	child->command_ptr = end;
+      }
+  }
+
+  if (argv == 0)
+    {
+      /* This line has no commands.  Go to the next.  */
+      start_job (child);
+      return;
+    }
+
+  /* Print out the command.  */
+
+  if (just_print_flag || (!noprint && !silent_flag))
+    puts (p);
+
+  /* If -n was given, recurse to get the next line in the sequence.  */
+
+  if (just_print_flag && !recursive)
+    {
+      free (argv[0]);
+      free ((char *) argv);
+      start_job (child);
+      return;
+    }
+
+  /* Flush the output streams so they won't have things written twice.  */
+
+  fflush (stdout);
+  fflush (stderr);
+  
+  /* Set up a bad standard input that reads from a broken pipe.  */
+
+  if (bad_stdin == -1)
+    {
+      /* Make a file descriptor that is the read end of a broken pipe.
+	 This will be used for some children's standard inputs.  */
+      int pd[2];
+      if (pipe (pd) == 0)
+	{
+	  /* Close the write side.  */
+	  (void) close (pd[1]);
+	  /* Save the read side.  */
+	  bad_stdin = pd[0];
+	}
+    }
+
+  /* Decide whether to give this child the `good' standard input
+     (one that points to the terminal or whatever), or the `bad' one
+     that points to the read side of a broken pipe.  */
+
+  child->good_stdin = !good_stdin_used;
+  if (child->good_stdin)
+    good_stdin_used = 1;
+
+  child->deleted = 0;
+
+  /* Set up the environment for the child.  */
+  if (child->environment == 0)
+    child->environment = target_environment (child->file);
+
+  if (start_remote_job_p ())
+    {
+      int is_remote, id, used_stdin;
+      if (start_remote_job (argv, child->good_stdin ? 0 : bad_stdin,
+			    &is_remote, &id, &used_stdin))
+	goto error;
+      else
+	{
+	  if (child->good_stdin && !used_stdin)
+	    {
+	      child->good_stdin = 0;
+	      good_stdin_used = 0;
+	    }
+	  child->remote = is_remote;
+	  child->pid = id;
+	}
+    }
+  else
+    {
+      if (child->command_line - 1 == 0)
+	{
+	  /* Wait for the load to be low enough if this
+	     is the first command in the sequence.  */
+	  make_access ();
+	  wait_to_start_job ();
+	  user_access ();
+	}
+
+      /* Fork the child process.  */
+
+      child->remote = 0;
+      child->pid = vfork ();
+      if (child->pid == 0)
+	/* We are the child side.  */
+	child_execute_job (child->good_stdin ? 0 : bad_stdin, 1,
+			   argv, child->environment);
+      else if (child->pid < 0)
+	{
+	  /* Fork failed!  */
+	  perror_with_name (VFORK_NAME, "");
+	  goto error;
+	}
+    }
+
+  /* We are the parent side.  Set the state to
+     say the commands are running and return.  */
+
+  child->file->command_state = cs_running;
+
+  /* Free the storage used by the child's argument list.  */
+
+  free (argv[0]);
+  free ((char *) argv);
+
+  return;
+
+ error:;
+  child->file->update_status = 1;
+  child->file->command_state = cs_finished;
+}
+
+
+/* Create a `struct child' for FILE and start its commands running.  */
+
+void
+new_job (file)
+     register struct file *file;
+{
+  register struct commands *cmds = file->cmds;
+  register struct child *c;
+  char **lines;
+  register unsigned int i;
+
+  /* Chop the commands up into lines if they aren't already.  */
+  chop_commands (cmds);
+
+  if (job_slots != 0)
+    /* Wait for a job slot to be freed up.  */
+    while (job_slots_used == job_slots)
+      wait_for_children (1, 0);
+
+  /* Expand the command lines and store the results in LINES.  */
+  lines = (char **) xmalloc (cmds->ncommand_lines * sizeof (char *));
+  for (i = 0; i < cmds->ncommand_lines; ++i)
+    lines[i] = allocated_variable_expand_for_file (cmds->command_lines[i],
+						   file);
+
+  /* Start the command sequence, record it in a new
+     `struct child', and add that to the chain.  */
+
+  push_signals_blocked_p (1);
+
+  c = (struct child *) xmalloc (sizeof (struct child));
+  c->file = file;
+  c->command_lines = lines;
+  c->command_line = 0;
+  c->command_ptr = 0;
+  c->environment = 0;
+  start_job (c);
+  switch (file->command_state)
+    {
+    case cs_running:
+      c->next = children;
+      children = c;
+      /* One more job slot is in use.  */
+      ++job_slots_used;
+      break;
+
+    case cs_finished:
+      free_child (c);
+      notice_finished_file (file);
+      break;
+
+    default:
+      error ("internal error: `%s' command_state == %d in new_job",
+	     file->name, (int) file->command_state);
+      abort ();
+      break;
+    }
+
+  pop_signals_blocked_p ();
+
+  if (job_slots == 1 && file->command_state == cs_running)
+    {
+      /* Since there is only one job slot, make things run linearly.
+	 Wait for the child to finish, setting the state to `cs_finished'.  */
+      while (file->command_state != cs_finished)
+	wait_for_children (1, 0);
+    }
+}
+
+/* Replace the current process with one executing the command in ARGV.
+   STDIN_FD and STDOUT_FD are used as the process's stdin and stdout; ENVP is
+   the environment of the new program.  This function does not return.  */
+
+void
+child_execute_job (stdin_fd, stdout_fd, argv, envp)
+     int stdin_fd, stdout_fd;
+     char **argv, **envp;
+{
+  if (stdin_fd != 0)
+    (void) dup2 (stdin_fd, 0);
+  if (stdout_fd != 1)
+    (void) dup2 (stdout_fd, 1);
+
+  /* Free up file descriptors.  */
+  {
+    register int d;
+    int max = getdtablesize ();
+    for (d = 3; d < max; ++d)
+      (void) close (d);
+  }
+
+  /* Don't block signals for the new process.  */
+  unblock_signals ();
+
+  /* Run the command.  */
+  exec_command (argv, envp);
+}
+
+/* Search PATH for FILE.
+   If successful, store the full pathname in PROGRAM and return 1.
+   If not sucessful, return zero.  */
+
+static int
+search_path (file, path, program)
+     char *file, *path, *program;
+{
+  if (path == 0 || path[0] == '\0')
+    path = default_path;
+
+  if (index (file, '/') != 0)
+    {
+      strcpy (program, file);
+      return 1;
+    }
+  else
+    {
+      unsigned int len;
+
+#if	!defined (USG) || defined (POSIX)
+#ifndef	POSIX
+      extern int getgroups ();
+#endif
+      static int ngroups = -1;
+#ifdef	NGROUPS_MAX
+      static GID_T groups[NGROUPS_MAX];
+#define	ngroups_max	NGROUPS_MAX
+#else
+      static GID_T *groups = 0;
+      static int ngroups_max;
+      if (groups == 0)
+	{
+	  ngroups_max = GET_NGROUPS_MAX;
+	  groups = (GID_T *) malloc (ngroups_max * sizeof (GID_T));
+	}
+#endif
+      if (groups != 0 && ngroups == -1)
+	ngroups = getgroups (ngroups_max, groups);
+#endif	/* POSIX or not USG.  */
+
+      len = strlen (file) + 1;
+      do
+	{
+	  struct stat st;
+	  int perm;
+	  char *p;
+
+	  p = index (path, ':');
+	  if (p == 0)
+	    p = path + strlen (path);
+
+	  if (p == path)
+	    bcopy (file, program, len);
+	  else
+	    {
+	      bcopy (path, program, p - path);
+	      program[p - path] = '/';
+	      bcopy (file, program + (p - path) + 1, len);
+	    }
+
+	  if (stat (program, &st) == 0
+	      && S_ISREG (st.st_mode))
+	    {
+	      if (st.st_uid == geteuid ())
+		perm = (st.st_mode & 0100);
+	      else if (st.st_gid == getegid ())
+		perm = (st.st_mode & 0010);
+	      else
+		{
+#ifndef	USG
+		  register int i;
+		  for (i = 0; i < ngroups; ++i)
+		    if (groups[i] == st.st_gid)
+		      break;
+		  if (i < ngroups)
+		    perm = (st.st_mode & 0010);
+		  else
+#endif	/* Not USG.  */
+		    perm = (st.st_mode & 0001);
+		}
+
+	      if (perm != 0)
+		return 1;
+	    }
+
+	  path = p + 1;
+	} while (*path != '\0');
+    }
+
+  return 0;
+}
+
+/* Replace the current process with one running the command in ARGV,
+   with environment ENVP.  This function does not return.  */
+
+void
+exec_command (argv, envp)
+     char **argv, **envp;
+{
+  char *shell, *path;
+  PATH_VAR (program);
+  register char **ep;
+
+  shell = path = 0;
+  for (ep = envp; *ep != 0; ++ep)
+    {
+      if (shell == 0 && !strncmp(*ep, "SHELL=", 6))
+	shell = &(*ep)[6];
+      else if (path == 0 && !strncmp(*ep, "PATH=", 5))
+	path = &(*ep)[5];
+      else if (path != 0 && shell != 0)
+	break;
+    }
+
+  /* Be the user, permanently.  */
+  child_access ();
+
+  if (!search_path (argv[0], path, program))
+    error ("%s: Command not found", argv[0]);
+  else
+    {
+      /* Run the program.  */
+      execve (program, argv, envp);
+
+      if (errno == ENOEXEC)
+	{
+	  PATH_VAR (shell_program);
+	  char *shell_path;
+	  if (shell == 0)
+	    shell_path = default_shell;
+	  else
+	    {
+	      if (search_path (shell, path, shell_program))
+		shell_path = shell_program;
+	      else
+		{
+		  shell_path = 0;
+		  error ("%s: Shell program not found", shell);
+		}
+	    }
+
+	  if (shell_path != 0)
+	    {
+	      char **new_argv;
+	      int argc;
+
+	      argc = 1;
+	      while (argv[argc] != 0)
+		++argc;
+
+	      new_argv = (char **) alloca ((1 + argc + 1) * sizeof (char *));
+	      new_argv[0] = shell_path;
+	      new_argv[1] = program;
+	      while (argc > 0)
+		{
+		  new_argv[1 + argc] = argv[argc];
+		  --argc;
+		}
+
+	      execve (shell_path, new_argv, envp);
+	      perror_with_name ("execve: ", shell_path);
+	    }
+	}
+      else
+	perror_with_name ("execve: ", program);
+    }
+
+  _exit (127);
+}
+
+/* Figure out the argument list necessary to run LINE as a command.
+   Try to avoid using a shell.  This routine handles only ' quoting.
+   Starting quotes may be escaped with a backslash.  If any of the
+   characters in sh_chars[] is seen, or any of the builtin commands
+   listed in sh_cmds[] is the first word of a line, the shell is used.
+
+   If RESTP is not NULL, *RESTP is set to point to the first newline in LINE.
+   If *RESTP is NULL, newlines will be ignored.
+
+   SHELL is the shell to use, or nil to use the default shell.
+   IFS is the value of $IFS, or nil (meaning the default).  */
+
+static char **
+construct_command_argv_internal (line, restp, shell, ifs)
+     char *line, **restp;
+     char *shell, *ifs;
+{
+  static char sh_chars[] = "#;\"*?[]&|<>(){}=$`";
+  static char *sh_cmds[] = { "cd", "eval", "exec", "exit", "login",
+			     "logout", "set", "umask", "wait", "while", "for",
+			     "case", "if", ":", ".", "break", "continue",
+			     "export", "read", "readonly", "shift", "times",
+			     "trap", "switch", 0 };
+  register int i;
+  register char *p;
+  register char *ap;
+  char *end;
+  int instring;
+  char **new_argv = 0;
+
+  if (restp != NULL)
+    *restp = NULL;
+
+  /* Make sure not to bother processing an empty line.  */
+  while (isblank (*line))
+    ++line;
+  if (*line == '\0')
+    return 0;
+
+  /* See if it is safe to parse commands internally.  */
+  if (shell == 0)
+    shell = default_shell;
+  else if (strcmp (shell, default_shell))
+    goto slow;
+
+  if (ifs != 0)
+    for (ap = ifs; *ap != '\0'; ++ap)
+      if (*ap != ' ' && *ap != '\t' && *ap != '\n')
+	goto slow;
+
+  i = strlen (line) + 1;
+
+  /* More than 1 arg per character is impossible.  */
+  new_argv = (char **) xmalloc (i * sizeof (char *));
+
+  /* All the args can fit in a buffer as big as LINE is.   */
+  ap = new_argv[0] = (char *) xmalloc (i);
+  end = ap + i;
+
+  /* I is how many complete arguments have been found.  */
+  i = 0;
+  instring = 0;
+  for (p = line; *p != '\0'; ++p)
+    {
+      if (ap > end)
+	abort ();
+
+      if (instring)
+	{
+	  /* Inside a string, just copy any char except a closing quote.  */
+	  if (*p == '\'')
+	    instring = 0;
+	  else
+	    *ap++ = *p;
+	}
+      else if (index (sh_chars, *p) != 0)
+	/* Not inside a string, but it's a special char.  */
+	goto slow;
+      else
+	/* Not a special char.  */
+	switch (*p)
+	  {
+	  case '\\':
+	    /* Backslash-newline combinations are eaten.  */
+	    if (p[1] == '\n')
+	      {
+		/* Eat the backslash, the newline, and following whitespace,
+		   replacing it all with a single space.  */
+		p += 2;
+
+		/* If there is a tab after a backslash-newline,
+		   remove it from the source line which will be echoed,
+		   since it was most likely used to line
+		   up the continued line with the previous one.  */
+		if (*p == '\t')
+		  strcpy (p, p + 1);
+
+		if (ap != new_argv[i])
+		  /* Treat this as a space, ending the arg.
+		     But if it's at the beginning of the arg, it should
+		     just get eaten, rather than becoming an empty arg. */
+		  goto end_of_arg;
+		else
+		  --p;
+	      }
+	    else if (p[1] != '\0')
+	      /* Copy and skip the following char.  */
+	      *ap++ = *++p;
+	    break;
+
+	  case '\'':
+	    instring = 1;
+	    break;
+
+	  case '\n':
+	    if (restp != NULL)
+	      {
+		/* End of the command line.  */
+		*restp = p;
+		goto end_of_line;
+	      }
+	    else
+	      /* Newlines are not special.  */
+	      *ap++ = '\n';
+	    break;
+
+	  case ' ':
+	  case '\t':
+	  end_of_arg:
+	    /* We have the end of an argument.
+	       Terminate the text of the argument.  */
+	    *ap++ = '\0';
+	    new_argv[++i] = ap;
+	    /* If this argument is the command name,
+	       see if it is a built-in shell command.
+	       If so, have the shell handle it.  */
+	    if (i == 1)
+	      {
+		register int j;
+		for (j = 0; sh_cmds[j] != 0; ++j)
+		  if (streq (sh_cmds[j], new_argv[0]))
+		    goto slow;
+	      }
+
+	    /* Ignore multiple whitespace chars.  */
+	    p = next_token (p);
+	    /* Next iteration should examine the first nonwhite char.  */
+	    --p;
+	    break;
+
+	  default:
+	    *ap++ = *p;
+	    break;
+	  }
+    }
+ end_of_line:
+
+  if (instring)
+    /* Let the shell deal with an unterminated quote.  */
+    goto slow;
+
+  /* Terminate the last argument and the argument list.  */
+
+  *ap = '\0';
+  if (new_argv[i][0] != '\0')
+    ++i;
+  new_argv[i] = 0;
+
+  if (new_argv[0] == 0)
+    /* Line was empty.  */
+    return 0;
+  else
+    return new_argv;
+
+ slow:;
+  /* We must use the shell.  */
+
+  if (new_argv != 0)
+    {
+      /* Free the old argument list we were working on.  */
+      free (new_argv[0]);
+      free (new_argv);
+    }
+
+  {
+    /* SHELL may be a multi-word command.  Construct a command line
+       "SHELL -c LINE", with all special chars in LINE escaped.
+       Then recurse, expanding this command line to get the final
+       argument list.  */
+    
+    unsigned int shell_len = strlen (shell);
+    static char minus_c[] = " -c ";
+    unsigned int line_len = strlen (line);
+    
+    char *new_line = (char *) alloca (shell_len + (sizeof (minus_c) - 1)
+				      + (line_len * 2) + 1);
+    
+    ap = new_line;
+    bcopy (shell, ap, shell_len);
+    ap += shell_len;
+    bcopy (minus_c, ap, sizeof (minus_c) - 1);
+    ap += sizeof (minus_c) - 1;
+    for (p = line; *p != '\0'; ++p)
+      {
+	if (restp != NULL && *p == '\n')
+	  {
+	    *restp = p;
+	    break;
+	  }
+	else if (*p == '\\' && p[1] == '\n')
+	  {
+	    /* Eat the backslash, the newline, and following whitespace,
+	       replacing it all with a single space (which is escaped
+	       from the shell).  */
+	    p += 2;
+
+	    /* If there is a tab after a backslash-newline,
+	       remove it from the source line which will be echoed,
+	       since it was most likely used to line
+	       up the continued line with the previous one.  */
+	    if (*p == '\t')
+	      strcpy (p, p + 1);
+
+	    p = next_token (p);
+	    --p;
+	    *ap++ = '\\';
+	    *ap++ = ' ';
+	    continue;
+	  }
+
+	if (*p == '\\' || *p == '\''
+	    || isspace (*p)
+	    || index (sh_chars, *p) != 0)
+	  *ap++ = '\\';
+	*ap++ = *p;
+      }
+    *ap = '\0';
+    
+    new_argv = construct_command_argv_internal (new_line, (char **) NULL,
+						(char *) 0, (char *) 0);
+  }
+
+  return new_argv;
+}
+
+/* Figure out the argument list necessary to run LINE as a command.
+   Try to avoid using a shell.  This routine handles only ' quoting.
+   Starting quotes may be escaped with a backslash.  If any of the
+   characters in sh_chars[] is seen, or any of the builtin commands
+   listed in sh_cmds[] is the first word of a line, the shell is used.
+
+   If RESTP is not NULL, *RESTP is set to point to the first newline in LINE.
+   If *RESTP is NULL, newlines will be ignored.
+
+   FILE is the target whose commands these are.  It is used for
+   variable expansion for $(SHELL) and $(IFS).  */
+
+char **
+construct_command_argv (line, restp, file)
+     char *line, **restp;
+     struct file *file;
+{
+  char *shell = allocated_variable_expand_for_file ("$(SHELL)", file);
+  char *ifs = allocated_variable_expand_for_file ("$(IFS)", file);
+  char **argv;
+
+  argv = construct_command_argv_internal (line, restp, shell, ifs);
+
+  free (shell);
+  free (ifs);
+
+  return argv;
+}
+
+#if	(defined(USG) && !defined(HAVE_SIGLIST)) || defined(DGUX)
+/* Initialize sys_siglist.  */
+
+void
+init_siglist ()
+{
+  char buf[100];
+  register unsigned int i;
+
+  for (i = 0; i < NSIG; ++i)
+    switch (i)
+      {
+      default:
+	sprintf (buf, "Signal %u", i);
+	sys_siglist[i] = savestring (buf, strlen (buf));
+	break;
+      case SIGHUP:
+	sys_siglist[i] = "Hangup";
+	break;
+      case SIGINT:
+	sys_siglist[i] = "Interrupt";
+	break;
+      case SIGQUIT:
+	sys_siglist[i] = "Quit";
+	break;
+      case SIGILL:
+	sys_siglist[i] = "Illegal Instruction";
+	break;
+      case SIGTRAP:
+	sys_siglist[i] = "Trace Trap";
+	break;
+      case SIGIOT:
+	sys_siglist[i] = "IOT Trap";
+	break;
+#ifdef	SIGEMT
+      case SIGEMT:
+	sys_siglist[i] = "EMT Trap";
+	break;
+#endif
+#ifdef	SIGDANGER
+      case SIGDANGER:
+	sys_siglist[i] = "Danger signal";
+	break;
+#endif
+      case SIGFPE:
+	sys_siglist[i] = "Floating Point Exception";
+	break;
+      case SIGKILL:
+	sys_siglist[i] = "Killed";
+	break;
+      case SIGBUS:
+	sys_siglist[i] = "Bus Error";
+	break;
+      case SIGSEGV:
+	sys_siglist[i] = "Segmentation fault";
+	break;
+      case SIGSYS:
+	sys_siglist[i] = "Bad Argument to System Call";
+	break;
+      case SIGPIPE:
+	sys_siglist[i] = "Broken Pipe";
+	break;
+      case SIGALRM:
+	sys_siglist[i] = "Alarm Clock";
+	break;
+      case SIGTERM:
+	sys_siglist[i] = "Terminated";
+	break;
+#if	!defined (SIGIO) || SIGUSR1 != SIGIO
+      case SIGUSR1:
+	sys_siglist[i] = "User-defined signal 1";
+	break;
+#endif
+#if	!defined (SIGURG) || SIGUSR2 != SIGURG
+      case SIGUSR2:
+	sys_siglist[i] = "User-defined signal 2";
+	break;
+#endif
+#ifdef	SIGCLD
+      case SIGCLD:
+#endif
+#if	defined(SIGCHLD) && !defined(SIGCLD)
+      case SIGCHLD:
+#endif
+	sys_siglist[i] = "Child Process Exited";
+	break;
+#ifdef	SIGPWR
+      case SIGPWR:
+	sys_siglist[i] = "Power Failure";
+	break;
+#endif
+#ifdef	SIGVTALRM
+      case SIGVTALRM:
+	sys_siglist[i] = "Virtual Timer Alarm";
+	break;
+#endif
+#ifdef	SIGPROF
+      case SIGPROF:
+	sys_siglist[i] = "Profiling Alarm Clock";
+	break;
+#endif
+#ifdef	SIGIO
+      case SIGIO:
+	sys_siglist[i] = "I/O Possible";
+	break;
+#endif
+#ifdef	SIGWINDOW
+      case SIGWINDOW:
+	sys_siglist[i] = "Window System Signal";
+	break;
+#endif
+#ifdef	SIGSTOP
+      case SIGSTOP:
+	sys_siglist[i] = "Stopped (signal)";
+	break;
+#endif
+#ifdef	SIGTSTP
+      case SIGTSTP:
+	sys_siglist[i] = "Stopped";
+	break;
+#endif
+#ifdef	SIGCONT
+      case SIGCONT:
+	sys_siglist[i] = "Continued";
+	break;
+#endif
+#ifdef	SIGTTIN
+      case SIGTTIN:
+	sys_siglist[i] = "Stopped (tty input)";
+	break;
+#endif
+#ifdef	SIGTTOU
+      case SIGTTOU:
+	sys_siglist[i] = "Stopped (tty output)";
+	break;
+#endif
+#ifdef	SIGURG
+      case SIGURG:
+	sys_siglist[i] = "Urgent Condition on Socket";
+	break;
+#endif
+#ifdef	SIGXCPU
+      case SIGXCPU:
+	sys_siglist[i] = "CPU Limit Exceeded";
+	break;
+#endif
+#ifdef	SIGXFSZ
+      case SIGXFSZ:
+	sys_siglist[i] = "File Size Limit Exceeded";
+	break;
+#endif
+      }
+}
+#endif	/* USG and not HAVE_SIGLIST.  */
+
+#if	defined(USG) && !defined(USGr3) && !defined(HAVE_DUP2)
+int
+dup2 (old, new)
+     int old, new;
+{
+  int fd;
+
+  (void) close (new);
+  fd = dup (old);
+  if (fd != new)
+    {
+      (void) close (fd);
+      errno = EMFILE;
+      return -1;
+    }
+
+  return fd;
+}
+#endif	/* USG and not USGr3 and not HAVE_DUP2.  */