No specific user configuration
[platform/upstream/bash.git] / jobs.c
diff --git a/jobs.c b/jobs.c
index d183394..f38b0c3 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -1,25 +1,25 @@
-/* The thing that makes children, remembers them, and contains wait loops. */
+/* jobs.c - functions that make children, remember them, and handle their termination. */
 
 /* This file works with both POSIX and BSD systems.  It implements job
    control. */
 
-/* Copyright (C) 1989-2006 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2013 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 2, or (at your option) any later
-   version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or
-   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-   for more details.
+   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, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
@@ -45,7 +45,9 @@
 
 #include "filecntl.h"
 #include <sys/ioctl.h>
+#if defined (HAVE_SYS_PARAM_H)
 #include <sys/param.h>
+#endif
 
 #if defined (BUFFERED_INPUT)
 #  include "input.h"
@@ -67,6 +69,7 @@
 #include "bashintl.h"
 #include "shell.h"
 #include "jobs.h"
+#include "execute_cmd.h"
 #include "flags.h"
 
 #include "builtins/builtext.h"
 extern int errno;
 #endif /* !errno */
 
-#define DEFAULT_CHILD_MAX 32
+#if !defined (HAVE_KILLPG)
+extern int killpg __P((pid_t, int));
+#endif
+
+#if !DEFAULT_CHILD_MAX
+#  define DEFAULT_CHILD_MAX 32
+#endif
+
+#if !MAX_CHILD_MAX
+#  define MAX_CHILD_MAX 8192
+#endif
+
 #if !defined (DEBUG)
 #define MAX_JOBS_IN_ARRAY 4096         /* production */
 #else
@@ -144,6 +158,7 @@ extern int subshell_environment, line_number;
 extern int posixly_correct, shell_level;
 extern int last_command_exit_value, last_command_exit_signal;
 extern int loop_level, breaking;
+extern int executing_list;
 extern int sourcelevel;
 extern int running_trap;
 extern sh_builtin_func_t *this_shell_builtin;
@@ -197,10 +212,10 @@ int previous_job = NO_JOB;
 #endif
 
 /* Last child made by the shell.  */
-pid_t last_made_pid = NO_PID;
+volatile pid_t last_made_pid = NO_PID;
 
 /* Pid of the last asynchronous child. */
-pid_t last_asynchronous_pid = NO_PID;
+volatile pid_t last_asynchronous_pid = NO_PID;
 
 /* The pipeline currently being built. */
 PROCESS *the_pipeline = (PROCESS *)NULL;
@@ -213,12 +228,10 @@ int already_making_children = 0;
 
 /* If this is non-zero, $LINES and $COLUMNS are reset after every process
    exits from get_tty_state(). */
-int check_window_size;
+int check_window_size = CHECKWINSIZE_DEFAULT;
 
 /* Functions local to this file. */
 
-static void run_sigchld_trap __P((int));
-
 static sighandler wait_sigint_handler __P((int));
 static sighandler sigchld_handler __P((int));
 static sighandler sigcont_sighandler __P((int));
@@ -246,8 +259,6 @@ static int find_job __P((pid_t, int, PROCESS **));
 static int print_job __P((JOB *, int, int, int));
 static int process_exit_status __P((WAIT));
 static int process_exit_signal __P((WAIT));
-static int job_exit_status __P((int));
-static int job_exit_signal __P((int));
 static int set_job_status_and_cleanup __P((int));
 
 static WAIT job_signal_status __P((int));
@@ -267,12 +278,12 @@ static void set_current_job __P((int));
 static void reset_current __P((void));
 static void set_job_running __P((int));
 static void setjstatus __P((int));
+static int maybe_give_terminal_to __P((pid_t, pid_t, int));
 static void mark_all_jobs_as_dead __P((void));
 static void mark_dead_jobs_as_notified __P((int));
 static void restore_sigint_handler __P((void));
 #if defined (PGRP_PIPE)
 static void pipe_read __P((int *));
-static void pipe_close __P((int *));
 #endif
 
 static struct pidstat *bgp_alloc __P((pid_t, int));
@@ -316,10 +327,6 @@ static int jobs_list_frozen;
 
 static char retcode_name_buffer[64];
 
-/* flags to detect pid wraparound */
-static pid_t first_pid = NO_PID;
-static int pid_wrap = -1;
-
 #if !defined (_POSIX_VERSION)
 
 /* These are definitions to map POSIX 1003.1 functions onto existing BSD
@@ -346,8 +353,6 @@ void
 init_job_stats ()
 {
   js = zerojs;
-  first_pid = NO_PID;
-  pid_wrap = -1;
 }
 
 /* Return the working directory for the current process.  Unlike
@@ -442,7 +447,7 @@ restore_pipeline (discard)
   old_pipeline = the_pipeline;
   the_pipeline = saved_pipeline;
   already_making_children = saved_already_making_children;
-  if (discard)
+  if (discard && old_pipeline)
     discard_pipeline (old_pipeline);
 }
 
@@ -455,7 +460,7 @@ start_pipeline ()
       cleanup_the_pipeline ();
       pipeline_pgrp = 0;
 #if defined (PGRP_PIPE)
-      pipe_close (pgrp_pipe);
+      sh_closepipe (pgrp_pipe);
 #endif
     }
 
@@ -463,7 +468,7 @@ start_pipeline ()
   if (job_control)
     {
       if (pipe (pgrp_pipe) == -1)
-       sys_error ("start_pipeline: pgrp pipe");
+       sys_error (_("start_pipeline: pgrp pipe"));
     }
 #endif
 }
@@ -485,7 +490,7 @@ stop_pipeline (async, deferred)
 
 #if defined (PGRP_PIPE)
   /* The parent closes the process group synchronization pipe. */
-  pipe_close (pgrp_pipe);
+  sh_closepipe (pgrp_pipe);
 #endif
 
   cleanup_dead_jobs ();
@@ -639,13 +644,13 @@ stop_pipeline (async, deferred)
           *
           */
          if (job_control && newjob->pgrp && (subshell_environment&SUBSHELL_ASYNC) == 0)
-           give_terminal_to (newjob->pgrp, 0);
+           maybe_give_terminal_to (shell_pgrp, newjob->pgrp, 0);
        }
     }
 
   stop_making_children ();
   UNBLOCK_CHILD (oset);
-  return (js.j_current);
+  return (newjob ? i : js.j_current);
 }
 
 /* Functions to manage the list of exited background pids whose status has
@@ -701,8 +706,8 @@ bgp_delete (pid)
   for (prev = p = bgpids.list; p; prev = p, p = p->next)
     if (p->pid == pid)
       {
-        prev->next = p->next;  /* remove from list */
-        break;
+       prev->next = p->next;   /* remove from list */
+       break;
       }
 
   if (p == 0)
@@ -771,7 +776,7 @@ bgp_prune ()
       bgpids.npid--;
     }
 }
-    
+
 /* Reset the values of js.j_lastj and js.j_firstj after one or both have
    been deleted.  The caller should check whether js.j_njobs is 0 before
    calling this.  This wraps around, but the rest of the code does not.  At
@@ -840,11 +845,17 @@ cleanup_dead_jobs ()
       if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
        delete_job (i, 0);
     }
+
+#if defined (COPROCESS_SUPPORT)
+  coproc_reap ();
+#endif
+
   UNQUEUE_SIGCHLD(os);
 }
 
 static int
 processes_in_job (job)
+     int job;
 {
   int nproc;
   register PROCESS *p;
@@ -878,7 +889,9 @@ delete_old_job (pid)
        delete_job (job, DEL_NOBGPID);
       else
        {
-         internal_warning (_("forked pid %d appears in running job %d"), pid, job);
+#ifdef DEBUG
+         internal_warning (_("forked pid %d appears in running job %d"), pid, job+1);
+#endif
          if (p)
            p->pid = 0;
        }
@@ -905,6 +918,7 @@ realloc_jobs_list ()
   BLOCK_CHILD (set, oset);
   nlist = (js.j_jobslots == nsize) ? jobs : (JOB **) xmalloc (nsize * sizeof (JOB *));
 
+  js.c_reaped = js.j_ndead = 0;
   for (i = j = 0; i < js.j_jobslots; i++)
     if (jobs[i])
       {
@@ -913,12 +927,18 @@ realloc_jobs_list ()
        if (i == js.j_previous)
          nprev = j;
        nlist[j++] = jobs[i];
+       if (jobs[i]->state == JDEAD)
+         {
+           js.j_ndead++;
+           js.c_reaped += processes_in_job (i);
+         }
       }
 
-#if defined (DEBUG)
+#if 0
   itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize);
   itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0);
-  itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, (j > 0) ? j - 1 : 0);
+  itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j);
+  itrace ("realloc_jobs_list: js.j_ndead %d js.c_reaped %d", js.j_ndead, js.c_reaped);
 #endif
 
   js.j_firstj = 0;
@@ -945,14 +965,14 @@ realloc_jobs_list ()
   if (js.j_current == NO_JOB || js.j_previous == NO_JOB || js.j_current > js.j_lastj || js.j_previous > js.j_lastj)
     reset_current ();
 
-#ifdef DEBUG
+#if 0
   itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous);
 #endif
 
   UNBLOCK_CHILD (oset);
 }
 
-/* Compact the jobs list by removing dead jobs.  Assumed that we have filled
+/* Compact the jobs list by removing dead jobs.  Assume that we have filled
    the jobs array to some predefined maximum.  Called when the shell is not
    the foreground process (subshell_environment != 0).  Returns the first
    available slot in the compacted list.  If that value is js.j_jobslots, then
@@ -968,7 +988,7 @@ compact_jobs_list (flags)
   reap_dead_jobs ();
   realloc_jobs_list ();
 
-#ifdef DEBUG
+#if 0
   itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
 #endif
 
@@ -1094,10 +1114,10 @@ add_process (name, pid)
     {
 #  ifdef DEBUG
       if (j == NO_JOB)
-       internal_warning ("add_process: process %5ld (%s) in the_pipeline", (long)p->pid, p->command);
+       internal_warning (_("add_process: process %5ld (%s) in the_pipeline"), (long)p->pid, p->command);
 #  endif
       if (PALIVE (p))
-        internal_warning ("add_process: pid %5ld (%s) marked as still alive", (long)p->pid, p->command);
+        internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command);
       p->running = PS_RECYCLED;                /* mark as recycled */
     }
 #endif
@@ -1121,6 +1141,33 @@ add_process (name, pid)
     }
 }
 
+/* Create a (dummy) PROCESS with NAME, PID, and STATUS, and make it the last
+   process in jobs[JID]->pipe.  Used by the lastpipe code. */
+void
+append_process (name, pid, status, jid)
+     char *name;
+     pid_t pid;
+     int status;
+     int jid;
+{
+  PROCESS *t, *p;
+
+  t = (PROCESS *)xmalloc (sizeof (PROCESS));
+  t->next = (PROCESS *)NULL;
+  t->pid = pid;
+  /* set process exit status using offset discovered by configure */
+  t->status = (status & 0xff) << WEXITSTATUS_OFFSET;
+  t->running = PS_DONE;
+  t->command = name;
+
+  js.c_reaped++;       /* XXX */
+
+  for (p = jobs[jid]->pipe; p->next != jobs[jid]->pipe; p = p->next)
+    ;
+  p->next = t;
+  t->next = jobs[jid]->pipe;
+}
+
 #if 0
 /* Take the last job and make it the first job.  Must be called with
    SIGCHLD blocked. */
@@ -1400,7 +1447,7 @@ j_strsignal (s)
   if (x == 0)
     {
       x = retcode_name_buffer;
-      sprintf (x, "Signal %d", s);
+      sprintf (x, _("Signal %d"), s);
     }
   return x;
 }
@@ -1414,20 +1461,20 @@ printable_job_status (j, p, format)
   static char *temp;
   int es;
 
-  temp = "Done";
+  temp = _("Done");
 
   if (STOPPED (j) && format == 0)
     {
       if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0))
-       temp = "Stopped";
+       temp = _("Stopped");
       else
        {
          temp = retcode_name_buffer;
-         sprintf (temp, "Stopped(%s)", signal_name (WSTOPSIG (p->status)));
+         sprintf (temp, _("Stopped(%s)"), signal_name (WSTOPSIG (p->status)));
        }
     }
   else if (RUNNING (j))
-    temp = "Running";
+    temp = _("Running");
   else
     {
       if (WIFSTOPPED (p->status))
@@ -1439,14 +1486,14 @@ printable_job_status (j, p, format)
          temp = retcode_name_buffer;
          es = WEXITSTATUS (p->status);
          if (es == 0)
-           strcpy (temp, "Done");
+           strcpy (temp, _("Done"));
          else if (posixly_correct)
-           sprintf (temp, "Done(%d)", es);
+           sprintf (temp, _("Done(%d)"), es);
          else
-           sprintf (temp, "Exit %d", es);
+           sprintf (temp, _("Exit %d"), es);
        }
       else
-       temp = "Unknown status";
+       temp = _("Unknown status");
     }
 
   return temp;
@@ -1533,7 +1580,7 @@ print_pipeline (p, job_index, format, stream)
              if ((WIFSTOPPED (show->status) == 0) &&
                  (WIFCONTINUED (show->status) == 0) &&
                  WIFCORED (show->status))
-               fprintf (stream, "(core dumped) ");
+               fprintf (stream, _("(core dumped) "));
            }
        }
 
@@ -1552,7 +1599,7 @@ print_pipeline (p, job_index, format, stream)
 
          if (strcmp (temp, jobs[job_index]->wd) != 0)
            fprintf (stream,
-             "  (wd: %s)", polite_directory_format (jobs[job_index]->wd));
+             _("  (wd: %s)"), polite_directory_format (jobs[job_index]->wd));
        }
 
       if (format || (p == last))
@@ -1668,9 +1715,12 @@ make_child (command, async_p)
      char *command;
      int async_p;
 {
+  int forksleep;
   sigset_t set, oset;
   pid_t pid;
 
+  /* XXX - block SIGTERM here and unblock in child after fork resets the
+     set of pending signals? */
   sigemptyset (&set);
   sigaddset (&set, SIGCHLD);
   sigaddset (&set, SIGINT);
@@ -1679,6 +1729,8 @@ make_child (command, async_p)
 
   making_children ();
 
+  forksleep = 1;
+
 #if defined (BUFFERED_INPUT)
   /* If default_buffered_input is active, we are reading a script.  If
      the command is asynchronous, we have already duplicated /dev/null
@@ -1689,8 +1741,27 @@ make_child (command, async_p)
     sync_buffered_stream (default_buffered_input);
 #endif /* BUFFERED_INPUT */
 
-  /* Create the child, handle severe errors. */
-  if ((pid = fork ()) < 0)
+  RESET_SIGTERM;
+
+  /* Create the child, handle severe errors.  Retry on EAGAIN. */
+  while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)
+    {
+      /* bash-4.2 */
+      /* If we can't create any children, try to reap some dead ones. */
+      waitchld (-1, 0);
+
+      sys_error ("fork: retry");
+      RESET_SIGTERM;
+
+      if (sleep (forksleep) != 0)
+       break;
+      forksleep <<= 1;
+    }
+
+  if (pid != 0)
+    RESET_SIGTERM;
+
+  if (pid < 0)
     {
       sys_error ("fork");
 
@@ -1701,6 +1772,7 @@ make_child (command, async_p)
       if (the_pipeline)
        kill_current_pipeline ();
 
+      last_command_exit_value = EX_NOEXEC;
       throw_to_top_level ();   /* Reset signals, etc. */
     }
 
@@ -1744,7 +1816,7 @@ make_child (command, async_p)
             B.4.3.3, p. 237 also covers this, in the context of job control
             shells. */
          if (setpgid (mypid, pipeline_pgrp) < 0)
-           sys_error ("child setpgid (%ld to %ld)", (long)mypid, (long)pipeline_pgrp);
+           sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)pipeline_pgrp);
 
          /* By convention (and assumption above), if
             pipeline_pgrp == shell_pgrp, we are making a child for
@@ -1777,14 +1849,18 @@ make_child (command, async_p)
 
 #if defined (PGRP_PIPE)
       /* Release the process group pipe, since our call to setpgid ()
-        is done.  The last call to pipe_close is done in stop_pipeline. */
-      pipe_close (pgrp_pipe);
+        is done.  The last call to sh_closepipe is done in stop_pipeline. */
+      sh_closepipe (pgrp_pipe);
 #endif /* PGRP_PIPE */
 
+#if 0
+      /* Don't set last_asynchronous_pid in the child */
       if (async_p)
-       last_asynchronous_pid = mypid;
+       last_asynchronous_pid = mypid;          /* XXX */
+      else
+#endif
 #if defined (RECYCLES_PIDS)
-      else if (last_asynchronous_pid == mypid)
+      if (last_asynchronous_pid == mypid)
         /* Avoid pid aliasing.  1 seems like a safe, unusual pid value. */
        last_asynchronous_pid = 1;
 #endif
@@ -1794,13 +1870,6 @@ make_child (command, async_p)
       /* In the parent.  Remember the pid of the child just created
         as the proper pgrp if this is the first child. */
 
-      if (first_pid == NO_PID)
-       first_pid = pid;
-      else if (pid_wrap == -1 && pid < first_pid)
-       pid_wrap = 0;
-      else if (pid_wrap == 0 && pid >= first_pid)
-       pid_wrap = 1;
-
       if (job_control)
        {
          if (pipeline_pgrp == 0)
@@ -1834,15 +1903,13 @@ make_child (command, async_p)
        last_asynchronous_pid = 1;
 #endif
 
-      if (pid_wrap > 0)
-       delete_old_job (pid);
+      /* Delete the saved status for any job containing this PID in case it's
+        been reused. */
+      delete_old_job (pid);
 
-#if !defined (RECYCLES_PIDS)
-      /* Only check for saved status if we've saved more than CHILD_MAX
-        statuses, unless the system recycles pids. */
-      if ((js.c_reaped + bgpids.npid) >= js.c_childmax)
-#endif
-       bgp_delete (pid);               /* new process, discard any saved status */
+      /* Perform the check for pid reuse unconditionally.  Some systems reuse
+         PIDs before giving a process CHILD_MAX/_SC_CHILD_MAX unique ones. */
+      bgp_delete (pid);                /* new process, discard any saved status */
 
       last_made_pid = pid;
 
@@ -1959,7 +2026,7 @@ get_tty_state ()
          /* Only print an error message if we're really interactive at
             this time. */
          if (interactive)
-           sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level);
+           sys_error ("[%ld: %d (%d)] tcgetattr", (long)getpid (), shell_level, tty);
 #endif
          return -1;
        }
@@ -1998,7 +2065,7 @@ set_tty_state ()
          /* Only print an error message if we're really interactive at
             this time. */
          if (interactive)
-           sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level);
+           sys_error ("[%ld: %d (%d)] tcsetattr", (long)getpid (), shell_level, tty);
          return -1;
        }
 #endif /* TERMIOS_TTY_DRIVER */
@@ -2092,7 +2159,7 @@ wait_for_single_pid (pid)
   return r;
 }
 
-/* Wait for all of the backgrounds of this shell to finish. */
+/* Wait for all of the background processes started by this shell to finish. */
 void
 wait_for_background_pids ()
 {
@@ -2150,6 +2217,10 @@ wait_for_background_pids ()
 #define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids
 static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;
 
+static int wait_sigint_received;
+static int child_caught_sigint;
+static int waiting_for_child;
+
 static void
 restore_sigint_handler ()
 {
@@ -2157,11 +2228,10 @@ restore_sigint_handler ()
     {
       set_signal_handler (SIGINT, old_sigint_handler);
       old_sigint_handler = INVALID_SIGNAL_HANDLER;
+      waiting_for_child = 0;
     }
 }
 
-static int wait_sigint_received;
-
 /* Handle SIGINT while we are waiting for children in a script to exit.
    The `wait' builtin should be interruptible, but all others should be
    effectively ignored (i.e. not cause the shell to exit). */
@@ -2174,7 +2244,7 @@ wait_sigint_handler (sig)
   if (interrupt_immediately ||
       (this_shell_builtin && this_shell_builtin == wait_builtin))
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      last_command_exit_value = 128+SIGINT;
       restore_sigint_handler ();
       /* If we got a SIGINT while in `wait', and SIGINT is trapped, do
         what POSIX.2 says (see builtins/wait.def for more info). */
@@ -2182,19 +2252,36 @@ wait_sigint_handler (sig)
          signal_is_trapped (SIGINT) &&
          ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler))
        {
-         interrupt_immediately = 0;
          trap_handler (SIGINT);        /* set pending_traps[SIGINT] */
          wait_signal_received = SIGINT;
-         longjmp (wait_intr_buf, 1);
+         if (interrupt_immediately)
+           {
+             interrupt_immediately = 0;
+             longjmp (wait_intr_buf, 1);
+           }
+         else
+           /* Let CHECK_WAIT_INTR handle it in wait_for/waitchld */
+           SIGRETURN (0);
        }
-      
-      ADDINTERRUPT;
-      QUIT;
+      else if (interrupt_immediately)
+       {
+         ADDINTERRUPT;
+         QUIT;
+       }
+      else /* wait_builtin but signal not trapped, treat as interrupt */
+       kill (getpid (), SIGINT);
     }
 
   /* XXX - should this be interrupt_state?  If it is, the shell will act
      as if it got the SIGINT interrupt. */
-  wait_sigint_received = 1;
+  if (waiting_for_child)
+    wait_sigint_received = 1;
+  else
+    {
+      last_command_exit_value = 128+SIGINT;
+      restore_sigint_handler ();
+      kill (getpid (), SIGINT);
+    }
 
   /* Otherwise effectively ignore the SIGINT and allow the running job to
      be killed. */
@@ -2248,6 +2335,7 @@ raw_job_exit_status (job)
 {
   register PROCESS *p;
   int fail;
+  WAIT ret;
 
   if (pipefail_opt)
     {
@@ -2255,11 +2343,13 @@ raw_job_exit_status (job)
       p = jobs[job]->pipe;
       do
        {
-         if (p->status != EXECUTION_SUCCESS) fail = p->status;
+         if (WSTATUS (p->status) != EXECUTION_SUCCESS)
+           fail = WSTATUS(p->status);
          p = p->next;
        }
       while (p != jobs[job]->pipe);
-      return fail;
+      WSTATUS (ret) = fail;
+      return ret;
     }
 
   for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
@@ -2270,14 +2360,14 @@ raw_job_exit_status (job)
 /* Return the exit status of job JOB.  This is the exit status of the last
    (rightmost) process in the job's pipeline, modified if the job was killed
    by a signal or stopped. */
-static int
+int
 job_exit_status (job)
      int job;
 {
   return (process_exit_status (raw_job_exit_status (job)));
 }
 
-static int
+int
 job_exit_signal (job)
      int job;
 {
@@ -2311,11 +2401,11 @@ wait_for (pid)
   WAIT s;
   register PROCESS *child;
   sigset_t set, oset;
-  register PROCESS *p;
 
   /* In the case that this code is interrupted, and we longjmp () out of it,
      we are relying on the code in throw_to_top_level () to restore the
      top-level signal mask. */
+  child = 0;
   BLOCK_CHILD (set, oset);
 
   /* Ignore interrupts while waiting for a job run without job control
@@ -2328,10 +2418,11 @@ wait_for (pid)
      substitution. */
 
   /* This is possibly a race condition -- should it go in stop_pipeline? */
-  wait_sigint_received = 0;
+  wait_sigint_received = child_caught_sigint = 0;
   if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB))
     {
       old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
+      waiting_for_child = 0;
       if (old_sigint_handler == SIG_IGN)
        set_signal_handler (SIGINT, old_sigint_handler);
     }
@@ -2340,6 +2431,11 @@ wait_for (pid)
 
   if (interactive && job_control == 0)
     QUIT;
+  /* Check for terminating signals and exit the shell if we receive one */
+  CHECK_TERMSIG;
+
+  /* Check for a trapped signal interrupting the wait builtin and jump out */
+  CHECK_WAIT_INTR;
 
   /* If we say wait_for (), then we have a record of this child somewhere.
      If it and none of its peers are running, don't call waitchld(). */
@@ -2347,7 +2443,8 @@ wait_for (pid)
   job = NO_JOB;
   do
     {
-      FIND_CHILD (pid, child);
+      if (pid != ANY_PID)
+       FIND_CHILD (pid, child);
 
       /* If this child is part of a job, then we are really waiting for the
         job to finish.  Otherwise, we are waiting for the child to finish.
@@ -2360,7 +2457,7 @@ wait_for (pid)
         has already exited before this is called, sigchld_handler will have
         called waitchld and the state will be set to JDEAD. */
 
-      if (PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
+      if (pid == ANY_PID || PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
        {
 #if defined (WAITPID_BROKEN)    /* SCOv4 */
          sigset_t suspend_set;
@@ -2378,10 +2475,18 @@ wait_for (pid)
          sigemptyset (&act.sa_mask);
          sigemptyset (&oact.sa_mask);
          act.sa_flags = 0;
-         sigaction (SIGCHLD, &act, &oact);
+#  if defined (SA_RESTART)
+         act.sa_flags |= SA_RESTART;
 #  endif
+         sigaction (SIGCHLD, &act, &oact);
+#  endif /* MUST_UNBLOCK_CHLD */
          queue_sigchld = 1;
-         r = waitchld (pid, 1);
+         waiting_for_child++;
+         r = waitchld (pid, 1);        /* XXX */
+         waiting_for_child--;
+#if 0
+itrace("wait_for: blocking wait for %d returns %d child = %p", (int)pid, r, child);
+#endif
 #  if defined (MUST_UNBLOCK_CHLD)
          sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
          sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
@@ -2390,6 +2495,7 @@ wait_for (pid)
          if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
            {
              termination_state = -1;
+             /* XXX - restore sigint handler here? */
              goto wait_for_return;
            }
 
@@ -2399,8 +2505,11 @@ wait_for (pid)
             if it exists, as JDEAD. */
          if (r == -1 && errno == ECHILD)
            {
-             child->running = PS_DONE;
-             child->status = 0;        /* XXX -- can't find true status */
+             if (child)
+               {
+                 child->running = PS_DONE;
+                 WSTATUS (child->status) = 0;  /* XXX -- can't find true status */
+               }
              js.c_living = 0;          /* no living child processes */
              if (job != NO_JOB)
                {
@@ -2408,6 +2517,11 @@ wait_for (pid)
                  js.c_reaped++;
                  js.j_ndead++;
                }
+             if (pid == ANY_PID)
+               {
+                 termination_state = -1;
+                 break;
+               }
            }
 #endif /* WAITPID_BROKEN */
        }
@@ -2418,9 +2532,22 @@ wait_for (pid)
         old SIGINT signal handler. */
       if (interactive && job_control == 0)
        QUIT;
+      /* Check for terminating signals and exit the shell if we receive one */
+      CHECK_TERMSIG;
+
+      /* Check for a trapped signal interrupting the wait builtin and jump out */
+      CHECK_WAIT_INTR;
+
+      if (pid == ANY_PID)
+        /* XXX - could set child but we don't have a handle on what waitchld
+          reaps.  Leave termination_state alone. */
+       goto wait_for_return;
     }
   while (PRUNNING (child) || (job != NO_JOB && RUNNING (job)));
 
+  /* Restore the original SIGINT signal handler before we return. */
+  restore_sigint_handler ();
+
   /* The exit state of the command is either the termination state of the
      child, or the termination state of the job.  If a job, the status
      of the last child in the pipeline is the significant one.  If the command
@@ -2500,7 +2627,7 @@ if (job == NO_JOB)
                 or until loop, act as if the shell received SIGINT as
                 well, so the loop can be broken.  This doesn't call the
                 SIGINT signal handler; maybe it should. */
-             if (signal_is_trapped (SIGINT) == 0 && loop_level)
+             if (signal_is_trapped (SIGINT) == 0 && (loop_level || (shell_compatibility_level > 32 && executing_list)))
                ADDINTERRUPT;
              else
                {
@@ -2509,17 +2636,18 @@ if (job == NO_JOB)
                }
            }
        }
-      else if ((subshell_environment & SUBSHELL_COMSUB) && wait_sigint_received)
+      else if ((subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PIPE)) && wait_sigint_received)
        {
          /* If waiting for a job in a subshell started to do command
-            substitution, simulate getting and being killed by the SIGINT to
-            pass the status back to our parent. */
+            substitution or to run a pipeline element that consists of
+            something like a while loop or a for loop, simulate getting
+            and being killed by the SIGINT to pass the status back to our
+            parent. */
          s = job_signal_status (job);
-       
-         if (WIFSIGNALED (s) && WTERMSIG (s) == SIGINT && signal_is_trapped (SIGINT) == 0)
+
+         if (child_caught_sigint == 0 && signal_is_trapped (SIGINT) == 0)
            {
              UNBLOCK_CHILD (oset);
-             restore_sigint_handler ();
              old_sigint_handler = set_signal_handler (SIGINT, SIG_DFL);
              if (old_sigint_handler == SIG_IGN)
                restore_sigint_handler ();
@@ -2527,6 +2655,8 @@ if (job == NO_JOB)
                kill (getpid (), SIGINT);
            }
        }
+      else if (interactive_shell == 0 && IS_FOREGROUND (job) && check_window_size)
+       get_new_window_size (0, (int *)0, (int *)0);
 
       /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
          signal handler path */
@@ -2545,9 +2675,6 @@ wait_for_return:
 
   UNBLOCK_CHILD (oset);
 
-  /* Restore the original SIGINT signal handler before we return. */
-  restore_sigint_handler ();
-
   return (termination_state);
 }
 
@@ -2580,6 +2707,76 @@ wait_for_job (job)
   return r;
 }
 
+/* Wait for any background job started by this shell to finish.  Very
+   similar to wait_for_background_pids().  Returns the exit status of
+   the next exiting job, -1 if there are no background jobs.  The caller
+   is responsible for translating -1 into the right return value. */
+int
+wait_for_any_job ()
+{
+  pid_t pid;
+  int i, r, waited_for;
+  sigset_t set, oset;
+
+  if (jobs_list_frozen)
+    return -1;
+
+  /* First see if there are any unnotified dead jobs that we can report on */
+  BLOCK_CHILD (set, oset);
+  for (i = 0; i < js.j_jobslots; i++)
+    {
+      if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) == 0)
+       {
+return_job:
+         r = job_exit_status (i);
+         notify_of_job_status ();              /* XXX */
+         delete_job (i, 0);
+#if defined (COPROCESS_SUPPORT)
+         coproc_reap ();
+#endif
+         UNBLOCK_CHILD (oset);
+         return r;
+       }
+    }
+  UNBLOCK_CHILD (oset);
+
+  /* At this point, we have no dead jobs in the jobs table.  Wait until we
+     get one, even if it takes multiple pids exiting. */
+  for (waited_for = 0;;)
+    {
+      /* Make sure there is a background job to wait for */
+      BLOCK_CHILD (set, oset);
+      for (i = 0; i < js.j_jobslots; i++)
+        if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+          break;
+      if (i == js.j_jobslots)
+       {
+         UNBLOCK_CHILD (oset);
+         return -1;
+       }
+
+      UNBLOCK_CHILD (oset);
+
+      QUIT;
+      CHECK_TERMSIG;
+      CHECK_WAIT_INTR;
+
+      errno = 0;
+      r = wait_for (ANY_PID);  /* special sentinel value for wait_for */
+      if (r == -1 && errno == ECHILD)
+       mark_all_jobs_as_dead ();
+       
+      /* Now we see if we have any dead jobs and return the first one */
+      BLOCK_CHILD (set, oset);
+      for (i = 0; i < js.j_jobslots; i++)
+       if (jobs[i] && DEADJOB (i))
+         goto return_job;
+      UNBLOCK_CHILD (oset);
+    }
+
+  return -1;
+}
+
 /* Print info about dead jobs, and then delete them from the list
    of known jobs.  This does not actually delete jobs when the
    shell is not interactive, because the dead jobs are not marked
@@ -2996,6 +3193,7 @@ waitchld (wpid, block)
   WAIT status;
   PROCESS *child;
   pid_t pid;
+
   int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
   static int wcontinued = WCONTINUED;  /* run-time fix for glibc problem */
 
@@ -3011,9 +3209,24 @@ waitchld (wpid, block)
                        : 0;
       if (sigchld || block == 0)
        waitpid_flags |= WNOHANG;
+
+      /* Check for terminating signals and exit the shell if we receive one */
       CHECK_TERMSIG;
+      /* Check for a trapped signal interrupting the wait builtin and jump out */
+      CHECK_WAIT_INTR;
+
+      if (block == 1 && queue_sigchld == 0 && (waitpid_flags & WNOHANG) == 0)
+       {
+         internal_warning (_("waitchld: turning on WNOHANG to avoid indefinite block"));
+         waitpid_flags |= WNOHANG;
+       }
+
       pid = WAITPID (-1, &status, waitpid_flags);
 
+#if 0
+if (wpid != -1 && block)
+  itrace("waitchld: blocking waitpid returns %d", pid);
+#endif
       /* WCONTINUED may be rejected by waitpid as invalid even when defined */
       if (wcontinued && pid < 0 && errno == EINVAL)
        {
@@ -3025,7 +3238,7 @@ waitchld (wpid, block)
         if it was non-zero before we called waitpid. */
       if (sigchld > 0 && (waitpid_flags & WNOHANG))
        sigchld--;
-  
+
       /* If waitpid returns -1 with errno == ECHILD, there are no more
         unwaited-for child processes of this shell. */
       if (pid < 0 && errno == ECHILD)
@@ -3036,12 +3249,30 @@ waitchld (wpid, block)
            break;
        }
 
+#if 0
+itrace("waitchld: waitpid returns %d block = %d", pid, block);
+#endif
       /* If waitpid returns 0, there are running children.  If it returns -1,
         the only other error POSIX says it can return is EINTR. */
       CHECK_TERMSIG;
+      CHECK_WAIT_INTR;
+
+      /* If waitpid returns -1/EINTR and the shell saw a SIGINT, then we
+        assume the child has blocked or handled SIGINT.  In that case, we
+        require the child to actually die due to SIGINT to act on the
+        SIGINT we received; otherwise we assume the child handled it and
+        let it go. */
+      if (pid < 0 && errno == EINTR && wait_sigint_received)
+       child_caught_sigint = 1;
+
       if (pid <= 0)
        continue;       /* jumps right to the test */
 
+      /* If the child process did die due to SIGINT, forget our assumption
+        that it caught or otherwise handled it. */
+      if (WIFSIGNALED (status) && WTERMSIG (status) == SIGINT)
+        child_caught_sigint = 0;
+
       /* children_exited is used to run traps on SIGCHLD.  We don't want to
          run the trap if a process is just being continued. */
       if (WIFCONTINUED(status) == 0)
@@ -3053,12 +3284,20 @@ waitchld (wpid, block)
       /* Locate our PROCESS for this pid. */
       child = find_process (pid, 1, &job);     /* want living procs only */
 
+#if defined (COPROCESS_SUPPORT)
+      coproc_pidchk (pid, WSTATUS(status));
+#endif
+
       /* It is not an error to have a child terminate that we did
         not have a record of.  This child could have been part of
         a pipeline in backquote substitution.  Even so, I'm not
         sure child is ever non-zero. */
       if (child == 0)
-       continue;
+       {
+         if (WIFEXITED (status) || WIFSIGNALED (status))
+           js.c_reaped++;
+         continue;
+       }
 
       /* Remember status, and whether or not the process is running. */
       child->status = status;
@@ -3096,7 +3335,29 @@ waitchld (wpid, block)
   /* Call a SIGCHLD trap handler for each child that exits, if one is set. */
   if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
       trap_list[SIGCHLD] != (char *)IGNORE_SIG)
-    run_sigchld_trap (children_exited);
+    {
+      if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin)
+       {
+         interrupt_immediately = 0;
+         trap_handler (SIGCHLD);       /* set pending_traps[SIGCHLD] */
+         wait_signal_received = SIGCHLD;
+         /* If we're in a signal handler, let CHECK_WAIT_INTR pick it up;
+            run_pending_traps will call run_sigchld_trap later  */
+         if (sigchld == 0)
+           longjmp (wait_intr_buf, 1);
+       }
+      /* If not in posix mode and not executing the wait builtin, queue the
+        signal for later handling.  Run the trap immediately if we are
+        executing the wait builtin, but don't break out of `wait'. */
+      else if (sigchld)        /* called from signal handler */
+       queue_sigchld_trap (children_exited);
+      else if (running_trap)
+       queue_sigchld_trap (children_exited);
+      else if (this_shell_builtin == wait_builtin)
+       run_sigchld_trap (children_exited);     /* XXX */
+      else
+       queue_sigchld_trap (children_exited);
+    }
 
   /* We have successfully recorded the useful information about this process
      that has just changed state.  If we notify asynchronously, and the job
@@ -3144,8 +3405,7 @@ set_job_status_and_cleanup (job)
 #endif
        {
          any_stopped = 1;
-         any_tstped |= interactive && job_control &&
-                           (WSTOPSIG (child->status) == SIGTSTP);
+         any_tstped |= job_control && (WSTOPSIG (child->status) == SIGTSTP);
        }
       child = child->next;
     }
@@ -3211,7 +3471,7 @@ set_job_status_and_cleanup (job)
         does not exit due to SIGINT, run the trap handler but do not
         otherwise act as if we got the interrupt. */
       if (wait_sigint_received && interactive_shell == 0 &&
-         WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
+         child_caught_sigint && IS_FOREGROUND (job) &&
          signal_is_trapped (SIGINT))
        {
          int old_frozen;
@@ -3233,7 +3493,8 @@ set_job_status_and_cleanup (job)
         signals are sent to process groups) or via kill(2) to the foreground
         process by another process (or itself).  If the shell did receive the
         SIGINT, it needs to perform normal SIGINT processing. */
-      else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
+      else if (wait_sigint_received &&
+             child_caught_sigint == 0 &&
              IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
        {
          int old_frozen;
@@ -3273,7 +3534,7 @@ set_job_status_and_cleanup (job)
                  temp_handler = trap_to_sighandler (SIGINT);
                restore_sigint_handler ();
              if (temp_handler == SIG_DFL)
-               termsig_handler (SIGINT);
+               termsig_handler (SIGINT);       /* XXX */
              else if (temp_handler != SIG_IGN)
                (*temp_handler) (SIGINT);
            }
@@ -3315,7 +3576,7 @@ setjstatus (j)
 #endif
 }
 
-static void
+void
 run_sigchld_trap (nchild)
      int nchild;
 {
@@ -3336,6 +3597,7 @@ run_sigchld_trap (nchild)
   unwind_protect_int (jobs_list_frozen);
   unwind_protect_pointer (the_pipeline);
   unwind_protect_pointer (subst_assign_varlist);
+  unwind_protect_pointer (this_shell_builtin);
 
   /* We have to add the commands this way because they will be run
      in reverse order of adding.  We don't want maybe_set_sigchld_trap ()
@@ -3346,15 +3608,20 @@ run_sigchld_trap (nchild)
   subst_assign_varlist = (WORD_LIST *)NULL;
   the_pipeline = (PROCESS *)NULL;
 
-  restore_default_signal (SIGCHLD);
+  running_trap = SIGCHLD + 1;
+
+  set_impossible_sigchld_trap ();
   jobs_list_frozen = 1;
   for (i = 0; i < nchild; i++)
     {
+#if 0
       interrupt_immediately = 1;
+#endif
       parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE);
     }
 
   run_unwind_frame ("SIGCHLD trap");
+  running_trap = 0;
 }
 
 /* Function to call when you want to notify people of changes
@@ -3433,13 +3700,16 @@ notify_of_job_status ()
            case JDEAD:
              if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
                  termsig != SIGINT &&
+#if defined (DONT_REPORT_SIGTERM)
+                 termsig != SIGTERM &&
+#endif
 #if defined (DONT_REPORT_SIGPIPE)
                  termsig != SIGPIPE &&
 #endif
                  signal_is_trapped (termsig) == 0)
                {
                  /* Don't print `0' for a line number. */
-                 fprintf (stderr, "%s: line %d: ", get_name_for_error (), (line_number == 0) ? 1 : line_number);
+                 fprintf (stderr, _("%s: line %d: "), get_name_for_error (), (line_number == 0) ? 1 : line_number);
                  pretty_print_job (job, JLIST_NONINTERACTIVE, stderr);
                }
              else if (IS_FOREGROUND (job))
@@ -3453,7 +3723,7 @@ notify_of_job_status ()
                      fprintf (stderr, "%s", j_strsignal (termsig));
 
                      if (WIFCORED (s))
-                       fprintf (stderr, " (core dumped)");
+                       fprintf (stderr, _(" (core dumped)"));
 
                      fprintf (stderr, "\n");
                    }
@@ -3465,7 +3735,7 @@ notify_of_job_status ()
                  pretty_print_job (job, JLIST_STANDARD, stderr);
                  if (dir && strcmp (dir, jobs[job]->wd) != 0)
                    fprintf (stderr,
-                            "(wd now: %s)\n", polite_directory_format (dir));
+                            _("(wd now: %s)\n"), polite_directory_format (dir));
                }
 
              jobs[job]->flags |= J_NOTIFIED;
@@ -3478,7 +3748,7 @@ notify_of_job_status ()
              pretty_print_job (job, JLIST_STANDARD, stderr);
              if (dir && (strcmp (dir, jobs[job]->wd) != 0))
                fprintf (stderr,
-                        "(wd now: %s)\n", polite_directory_format (dir));
+                        _("(wd now: %s)\n"), polite_directory_format (dir));
              jobs[job]->flags |= J_NOTIFIED;
              break;
 
@@ -3502,16 +3772,20 @@ int
 initialize_job_control (force)
      int force;
 {
+  pid_t t;
+  int t_errno;
+
+  t_errno = -1;
   shell_pgrp = getpgid (0);
 
   if (shell_pgrp == -1)
     {
-      sys_error ("initialize_job_control: getpgrp failed");
+      sys_error (_("initialize_job_control: getpgrp failed"));
       exit (1);
     }
 
-  /* We can only have job control if we are interactive. */
-  if (interactive == 0)
+  /* We can only have job control if we are interactive unless we force it. */
+  if (interactive == 0 && force == 0)
     {
       job_control = 0;
       original_pgrp = NO_PID;
@@ -3519,12 +3793,24 @@ initialize_job_control (force)
     }
   else
     {
+      shell_tty = -1;
+
+      /* If forced_interactive is set, we skip the normal check that stderr
+        is attached to a tty, so we need to check here.  If it's not, we
+        need to see whether we have a controlling tty by opening /dev/tty,
+        since trying to use job control tty pgrp manipulations on a non-tty
+        is going to fail. */
+      if (forced_interactive && isatty (fileno (stderr)) == 0)
+       shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK);
+
       /* Get our controlling terminal.  If job_control is set, or
         interactive is set, then this is an interactive shell no
         matter where fd 2 is directed. */
-      shell_tty = dup (fileno (stderr));       /* fd 2 */
+      if (shell_tty == -1)
+       shell_tty = dup (fileno (stderr));      /* fd 2 */
 
-      shell_tty = move_to_high_fd (shell_tty, 1, -1);
+      if (shell_tty != -1)
+       shell_tty = move_to_high_fd (shell_tty, 1, -1);
 
       /* Compensate for a bug in systems that compiled the BSD
         rlogind with DEBUG defined, like NeXT and Alliant. */
@@ -3549,10 +3835,13 @@ initialize_job_control (force)
          break;
        }
 
+      if (terminal_pgrp == -1)
+       t_errno = errno;
+
       /* Make sure that we are using the new line discipline. */
       if (set_new_line_discipline (shell_tty) < 0)
        {
-         sys_error ("initialize_job_control: line discipline");
+         sys_error (_("initialize_job_control: line discipline"));
          job_control = 0;
        }
       else
@@ -3562,7 +3851,7 @@ initialize_job_control (force)
 
          if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0))
            {
-             sys_error ("initialize_job_control: setpgid");
+             sys_error (_("initialize_job_control: setpgid"));
              shell_pgrp = original_pgrp;
            }
 
@@ -3579,11 +3868,22 @@ initialize_job_control (force)
            {
              if (give_terminal_to (shell_pgrp, 0) < 0)
                {
+                 t_errno = errno;
                  setpgid (0, original_pgrp);
                  shell_pgrp = original_pgrp;
+                 errno = t_errno;
+                 sys_error (_("cannot set terminal process group (%d)"), shell_pgrp);
                  job_control = 0;
                }
            }
+
+         if (job_control && ((t = tcgetpgrp (shell_tty)) == -1 || t != shell_pgrp))
+           {
+             if (t_errno != -1)
+               errno = t_errno;
+             sys_error (_("cannot set terminal process group (%d)"), t);
+             job_control = 0;
+           }
        }
       if (job_control == 0)
        internal_error (_("no job control in this shell"));
@@ -3733,7 +4033,7 @@ give_terminal_to (pgrp, force)
      int force;
 {
   sigset_t set, oset;
-  int r;
+  int r, e;
 
   r = 0;
   if (job_control || force)
@@ -3754,15 +4054,47 @@ give_terminal_to (pgrp, force)
            shell_tty, (long)getpid(), (long)pgrp);
 #endif
          r = -1;
+         e = errno;
        }
       else
        terminal_pgrp = pgrp;
       sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
     }
 
+  if (r == -1)
+    errno = e;
+
   return r;
 }
 
+/* Give terminal to NPGRP iff it's currently owned by OPGRP.  FLAGS are the
+   flags to pass to give_terminal_to(). */
+static int
+maybe_give_terminal_to (opgrp, npgrp, flags)
+     pid_t opgrp, npgrp;
+     int flags;
+{
+  int tpgrp;
+
+  tpgrp = tcgetpgrp (shell_tty);
+  if (tpgrp < 0 && errno == ENOTTY)
+    return -1;
+  if (tpgrp == npgrp)
+    {
+      terminal_pgrp = npgrp;
+      return 0;
+    }
+  else if (tpgrp != opgrp)
+    {
+#if defined (DEBUG)
+      internal_warning ("maybe_give_terminal_to: terminal pgrp == %d shell pgrp = %d new pgrp = %d", tpgrp, opgrp, npgrp);
+#endif
+      return -1;
+    }
+  else
+    return (give_terminal_to (npgrp, flags));     
+}
+
 /* Clear out any jobs in the job array.  This is intended to be used by
    children of the shell, who should not have any job structures as baggage
    when they start executing (forking subshells for parenthesized execution
@@ -3930,8 +4262,10 @@ mark_dead_jobs_as_notified (force)
     }
 
 #ifdef DEBUG
+# if 0
   if (ndeadproc != js.c_reaped)
     itrace("mark_dead_jobs_as_notified: ndeadproc (%d) != js.c_reaped (%d)", ndeadproc, js.c_reaped);
+# endif
   if (ndead != js.j_ndead)
     itrace("mark_dead_jobs_as_notified: ndead (%d) != js.j_ndead (%d)", ndead, js.j_ndead);
 #endif
@@ -3989,7 +4323,13 @@ itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", j
 }
 
 /* Here to allow other parts of the shell (like the trap stuff) to
-   unfreeze the jobs list. */
+   freeze and unfreeze the jobs list. */
+void
+freeze_jobs_list ()
+{
+  jobs_list_frozen = 1;
+}
+
 void
 unfreeze_jobs_list ()
 {
@@ -4023,7 +4363,7 @@ without_job_control ()
   stop_making_children ();
   start_pipeline ();
 #if defined (PGRP_PIPE)
-  pipe_close (pgrp_pipe);
+  sh_closepipe (pgrp_pipe);
 #endif
   delete_all_jobs (0);
   set_job_control (0);
@@ -4035,7 +4375,7 @@ without_job_control ()
 void
 end_job_control ()
 {
-  if (interactive_shell)               /* XXX - should it be interactive? */
+  if (interactive_shell || job_control)                /* XXX - should it be just job_control? */
     {
       terminate_stopped_jobs ();
 
@@ -4057,6 +4397,27 @@ restart_job_control ()
   initialize_job_control (0);
 }
 
+void
+set_maxchild (nchild)
+     int nchild;
+{
+  static int lmaxchild = -1;
+
+  if (lmaxchild < 0)
+    lmaxchild = getmaxchild ();
+  if (lmaxchild < 0)
+    lmaxchild = DEFAULT_CHILD_MAX;
+
+  /* Clamp value we set.  Minimum is what Posix requires, maximum is defined
+     above as MAX_CHILD_MAX. */
+  if (nchild < lmaxchild)
+    nchild = lmaxchild;
+  else if (nchild > MAX_CHILD_MAX)
+    nchild = MAX_CHILD_MAX;
+
+  js.c_childmax = nchild;
+}
+
 /* Set the handler to run when the shell receives a SIGCHLD signal. */
 void
 set_sigchld_handler ()
@@ -4086,25 +4447,30 @@ pipe_read (pp)
     }
 }
 
-/* Close the read and write ends of PP, an array of file descriptors. */
-static void
-pipe_close (pp)
-     int *pp;
+/* Functional interface closes our local-to-job-control pipes. */
+void
+close_pgrp_pipe ()
 {
-  if (pp[0] >= 0)
-    close (pp[0]);
-
-  if (pp[1] >= 0)
-    close (pp[1]);
+  sh_closepipe (pgrp_pipe);
+}
 
-  pp[0] = pp[1] = -1;
+void
+save_pgrp_pipe (p, clear)
+     int *p;
+     int clear;
+{
+  p[0] = pgrp_pipe[0];
+  p[1] = pgrp_pipe[1];
+  if (clear)
+    pgrp_pipe[0] = pgrp_pipe[1] = -1;
 }
 
-/* Functional interface closes our local-to-job-control pipes. */
 void
-close_pgrp_pipe ()
+restore_pgrp_pipe (p)
+     int *p;
 {
-  pipe_close (pgrp_pipe);
+  pgrp_pipe[0] = p[0];
+  pgrp_pipe[1] = p[1];
 }
 
 #endif /* PGRP_PIPE */