]> git.ipfire.org Git - thirdparty/make.git/commitdiff
Initial revision
authorRoland McGrath <roland@redhat.com>
Mon, 20 Jan 1992 18:49:36 +0000 (18:49 +0000)
committerRoland McGrath <roland@redhat.com>
Mon, 20 Jan 1992 18:49:36 +0000 (18:49 +0000)
job.c [new file with mode: 0644]

diff --git a/job.c b/job.c
new file mode 100644 (file)
index 0000000..5019202
--- /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 ();
+\f
+/* 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;
+\f
+/* 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);
+    }
+}
+\f
+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 ();
+}
+\f
+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 ();
+}
+\f
+/* 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);
+}
+\f
+/* 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);
+    }
+}
+\f
+/* 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);
+}
+\f
+/* 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);
+}
+\f
+/* 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;
+}
+\f
+#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.  */