No specific user configuration
[platform/upstream/bash.git] / jobs.c
diff --git a/jobs.c b/jobs.c
index 3de0f4c..f38b0c3 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -3,7 +3,7 @@
 /* This file works with both POSIX and BSD systems.  It implements job
    control. */
 
-/* Copyright (C) 1989-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2013 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -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"
 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
@@ -199,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;
@@ -215,7 +228,7 @@ 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. */
 
@@ -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));
@@ -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);
 }
 
@@ -645,7 +650,7 @@ stop_pipeline (async, deferred)
 
   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)
@@ -840,6 +845,11 @@ 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);
 }
 
@@ -879,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;
        }
@@ -922,7 +934,7 @@ realloc_jobs_list ()
          }
       }
 
-#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);
@@ -953,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
@@ -976,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
 
@@ -1129,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. */
@@ -1680,6 +1719,8 @@ make_child (command, async_p)
   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);
@@ -1700,15 +1741,26 @@ make_child (command, async_p)
     sync_buffered_stream (default_buffered_input);
 #endif /* BUFFERED_INPUT */
 
+  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");
@@ -1720,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. */
     }
 
@@ -1817,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)
@@ -1857,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;
 
@@ -2115,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 ()
 {
@@ -2173,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 ()
 {
@@ -2180,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). */
@@ -2197,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). */
@@ -2205,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. */
@@ -2296,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;
 {
@@ -2337,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
@@ -2354,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);
     }
@@ -2366,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(). */
@@ -2373,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.
@@ -2386,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;
@@ -2404,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);
@@ -2416,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;
            }
 
@@ -2425,8 +2505,11 @@ wait_for (pid)
             if it exists, as JDEAD. */
          if (r == -1 && errno == ECHILD)
            {
-             child->running = PS_DONE;
-             WSTATUS (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)
                {
@@ -2434,6 +2517,11 @@ wait_for (pid)
                  js.c_reaped++;
                  js.j_ndead++;
                }
+             if (pid == ANY_PID)
+               {
+                 termination_state = -1;
+                 break;
+               }
            }
 #endif /* WAITPID_BROKEN */
        }
@@ -2444,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
@@ -2535,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 ();
@@ -2553,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 */
@@ -2571,9 +2675,6 @@ wait_for_return:
 
   UNBLOCK_CHILD (oset);
 
-  /* Restore the original SIGINT signal handler before we return. */
-  restore_sigint_handler ();
-
   return (termination_state);
 }
 
@@ -2606,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
@@ -3022,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 */
 
@@ -3037,10 +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)
        {
@@ -3052,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)
@@ -3063,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)
@@ -3081,7 +3285,7 @@ waitchld (wpid, block)
       child = find_process (pid, 1, &job);     /* want living procs only */
 
 #if defined (COPROCESS_SUPPORT)
-      coproc_pidchk (pid);
+      coproc_pidchk (pid, WSTATUS(status));
 #endif
 
       /* It is not an error to have a child terminate that we did
@@ -3132,15 +3336,27 @@ waitchld (wpid, block)
   if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
       trap_list[SIGCHLD] != (char *)IGNORE_SIG)
     {
-      if (this_shell_builtin && this_shell_builtin == wait_builtin)
+      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;
-         longjmp (wait_intr_buf, 1);
+         /* 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);
        }
-
-      run_sigchld_trap (children_exited);
+      /* 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
@@ -3189,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;
     }
@@ -3256,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;
@@ -3278,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;
@@ -3318,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);
            }
@@ -3381,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 ()
@@ -3391,15 +3608,20 @@ run_sigchld_trap (nchild)
   subst_assign_varlist = (WORD_LIST *)NULL;
   the_pipeline = (PROCESS *)NULL;
 
+  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
@@ -3478,6 +3700,9 @@ 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
@@ -3559,8 +3784,8 @@ initialize_job_control (force)
       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;
@@ -3584,7 +3809,8 @@ initialize_job_control (force)
       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. */
@@ -3645,6 +3871,8 @@ initialize_job_control (force)
                  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;
                }
            }
@@ -4034,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
@@ -4093,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 ()
 {
@@ -4139,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 ();
 
@@ -4161,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 ()
@@ -4197,4 +4454,23 @@ close_pgrp_pipe ()
   sh_closepipe (pgrp_pipe);
 }
 
+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;
+}
+
+void
+restore_pgrp_pipe (p)
+     int *p;
+{
+  pgrp_pipe[0] = p[0];
+  pgrp_pipe[1] = p[1];
+}
+
 #endif /* PGRP_PIPE */