Imported from ../bash-2.05a.tar.gz.
[platform/upstream/bash.git] / jobs.c
diff --git a/jobs.c b/jobs.c
index e1340c4..d50db68 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -9,7 +9,7 @@
 
    Bash is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
-   Software Foundation; either version 1, or (at your option) any later
+   Software Foundation; either version 2, or (at your option) any later
    version.
 
    Bash is distributed in the hope that it will be useful, but WITHOUT ANY
@@ -19,7 +19,7 @@
 
    You should have received a copy of the GNU General Public License along
    with Bash; see the file COPYING.  If not, write to the Free Software
-   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
 
 #include "config.h"
 
@@ -33,9 +33,7 @@
 #  include <unistd.h>
 #endif
 
-#if defined (HAVE_SYS_TIME_H)
-#  include <sys/time.h>
-#endif
+#include "posixtime.h"
 
 #if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE)
 #  include <sys/resource.h>
 #endif
 
 /* Need to include this up here for *_TTY_DRIVER definitions. */
-#include "bashtty.h"
+#include "shtty.h"
 
 /* Define this if your output is getting swallowed.  It's a no-op on
    machines with the termio or termios tty drivers. */
 /* #define DRAIN_OUTPUT */
 
-/* The _POSIX_SOURCE define is to avoid multiple symbol definitions
-   between sys/ioctl.h and termios.h.  Ditto for the test against SunOS4
-   and the undefining of several symbols. */
-#if defined (TERMIOS_TTY_DRIVER)
-#  if (defined (SunOS4) || defined (SunOS5)) && !defined (_POSIX_SOURCE)
-#    define _POSIX_SOURCE
-#  endif
-#  if defined (SunOS4)
-#    undef ECHO
-#    undef NOFLSH
-#    undef TOSTOP
-#  endif /* SunOS4 */
-#  include <termios.h>
-#else /* !TERMIOS_TTY_DRIVER */
-#  if defined (TERMIO_TTY_DRIVER)
-#    include <termio.h>
-#  else
-#    include <sgtty.h>
-#  endif
-#endif /* !TERMIOS_TTY_DRIVER */
-
 /* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */
 #if defined (hpux) && !defined (TERMIOS_TTY_DRIVER)
 #  include <bsdtty.h>
@@ -98,7 +75,6 @@
 #include "shell.h"
 #include "jobs.h"
 #include "flags.h"
-#include "error.h"
 
 #include "builtins/builtext.h"
 #include "builtins/common.h"
 extern int errno;
 #endif /* !errno */
 
+#if !defined (CHILD_MAX)
+#  define CHILD_MAX 32
+#endif
+
 /* Take care of system dependencies that must be handled when waiting for
    children.  The arguments to the WAITPID macro match those to the Posix.1
    waitpid() function. */
@@ -145,29 +125,37 @@ extern int errno;
 #  define REINSTALL_SIGCHLD_HANDLER
 #endif /* !MUST_REINSTALL_SIGHANDLERS */
 
+/* Some systems let waitpid(2) tell callers about stopped children. */
+#if !defined (WCONTINUED)
+#  define WCONTINUED 0
+#  define WIFCONTINUED(s)      (0)
+#endif
+
 /* The number of additional slots to allocate when we run out. */
 #define JOB_SLOTS 8
 
+typedef int sh_job_map_func_t __P((JOB *, int, int, int));
+
 #if defined (READLINE)
-extern void _rl_set_screen_size ();
+extern void rl_set_screen_size __P((int, int));
 #endif
 
 /* Variables used here but defined in other files. */
-extern int interactive, interactive_shell, asynchronous_notification;
 extern int startup_state, subshell_environment, line_number;
-extern int posixly_correct, no_symbolic_links, shell_level;
+extern int posixly_correct, shell_level;
 extern int interrupt_immediately, last_command_exit_value;
 extern int loop_level, breaking;
-extern Function *this_shell_builtin;
+extern int sourcelevel;
+extern sh_builtin_func_t *this_shell_builtin;
 extern char *shell_name, *this_command_name;
 extern sigset_t top_level_mask;
+extern procenv_t wait_intr_buf;
+extern WORD_LIST *subst_assign_varlist;
 
 #if defined (ARRAY_VARS)
 static int *pstatuses;         /* list of pipeline statuses */
 static int statsize;
 #endif
-static void setjstatus ();
-static void get_new_window_size ();
 
 /* The array of known jobs. */
 JOB **jobs = (JOB **)NULL;
@@ -223,21 +211,57 @@ int already_making_children = 0;
 int check_window_size;
 
 /* Functions local to this file. */
-static sighandler sigchld_handler ();
-static int waitchld ();
-static PROCESS *find_pipeline ();
-static char *current_working_directory ();
-static char *job_working_directory ();
-static pid_t find_last_pid (), last_pid ();
-static int set_new_line_discipline (), map_over_jobs (), last_running_job ();
-static int most_recent_job_in_state (), last_stopped_job (), find_job ();
-static void notify_of_job_status (), cleanup_dead_jobs (), discard_pipeline ();
-static void add_process (), set_current_job (), reset_current ();
-static void print_pipeline ();
-static void pretty_print_job ();
-static void mark_dead_jobs_as_notified ();
+
+static void get_new_window_size __P((int));
+
+static void run_sigchld_trap __P((int));
+
+static sighandler wait_sigint_handler __P((int));
+static sighandler sigchld_handler __P((int));
+static sighandler sigwinch_sighandler __P((int));
+static sighandler sigcont_sighandler __P((int));
+static sighandler sigstop_sighandler __P((int));
+
+static int waitchld __P((pid_t, int));
+
+static PROCESS *find_pipeline __P((pid_t));
+
+static char *current_working_directory __P((void));
+static char *job_working_directory __P((void));
+static char *printable_job_status __P((int, PROCESS *, int));
+
+static pid_t find_last_pid __P((int));
+static pid_t last_pid __P((int));
+
+static int set_new_line_discipline __P((int));
+static int map_over_jobs __P((sh_job_map_func_t *, int, int));
+static int job_last_stopped __P((int));
+static int job_last_running __P((int));
+static int most_recent_job_in_state __P((int, JOB_STATE));
+static int find_job __P((pid_t));
+static int print_job __P((JOB *, int, int, int));
+static int process_exit_status __P((WAIT));
+static int job_exit_status __P((int));
+static int set_job_status_and_cleanup __P((int));
+
+static WAIT raw_job_exit_status __P((int));
+
+static void notify_of_job_status __P((void));
+static void cleanup_dead_jobs __P((void));
+static void discard_pipeline __P((PROCESS *));
+static void add_process __P((char *, pid_t));
+static void print_pipeline __P((PROCESS *, int, int, FILE *));
+static void pretty_print_job __P((int, int, FILE *));
+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 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 (), pipe_close ();
+static void pipe_read __P((int *));
+static void pipe_close __P((int *));
 #endif
 
 /* Used to synchronize between wait_for and the SIGCHLD signal handler. */
@@ -254,6 +278,8 @@ static int saved_already_making_children;
    commands. */
 static int jobs_list_frozen;
 
+static char retcode_name_buffer[64];
+
 #if !defined (_POSIX_VERSION)
 
 /* These are definitions to map POSIX 1003.1 functions onto existing BSD
@@ -490,7 +516,7 @@ stop_pipeline (async, deferred)
       newjob->wd = job_working_directory ();
       newjob->deferred = deferred;
 
-      newjob->j_cleanup = (VFunction *)NULL;
+      newjob->j_cleanup = (sh_vptrfunc_t *)NULL;
       newjob->cleanarg = (PTR_T) NULL;
 
       jobs[i] = newjob;
@@ -521,7 +547,7 @@ stop_pipeline (async, deferred)
           *
           */
          if (job_control && newjob->pgrp)
-           give_terminal_to (newjob->pgrp);
+           give_terminal_to (newjob->pgrp, 0);
        }
     }
 
@@ -543,12 +569,7 @@ cleanup_dead_jobs ()
   BLOCK_CHILD (set, oset);
 
   for (i = 0; i < job_slots; i++)
-#if 0
-    if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) &&
-         (interactive_shell || (find_last_pid (i) != last_asynchronous_pid)))
-#else
     if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
-#endif
       delete_job (i, 0);
 
   UNBLOCK_CHILD (oset);
@@ -566,7 +587,7 @@ delete_job (job_index, warn_stopped)
     return;
 
   if (warn_stopped && subshell_environment == 0 && STOPPED (job_index))
-    internal_warning ("deleting stopped job %d with process group %d", job_index+1, jobs[job_index]->pgrp);
+    internal_warning ("deleting stopped job %d with process group %ld", job_index+1, (long)jobs[job_index]->pgrp);
 
   temp = jobs[job_index];
   if (job_index == current_job || job_index == previous_job)
@@ -688,13 +709,16 @@ reverse_the_pipeline ()
    and INDEX. */
 static int
 map_over_jobs (func, arg1, arg2)
-     Function *func;
+     sh_job_map_func_t *func;
      int arg1, arg2;
 {
   register int i;
   int result;
   sigset_t set, oset;
 
+  if (job_slots == 0)
+    return 0;
+
   BLOCK_CHILD (set, oset);
 
   for (i = result = 0; i < job_slots; i++)
@@ -855,13 +879,60 @@ describe_pid (pid)
   job = find_job (pid);
 
   if (job != NO_JOB)
-    printf ("[%d] %d\n", job + 1, (int)pid);
+    printf ("[%d] %ld\n", job + 1, (long)pid);
   else
-    programming_error ("describe_pid: %d: no such pid", (int)pid);
+    programming_error ("describe_pid: %ld: no such pid", (long)pid);
 
   UNBLOCK_CHILD (oset);
 }
 
+static char *
+printable_job_status (j, p, format)
+     int j;
+     PROCESS *p;
+     int format;
+{
+  static char *temp;
+  int es;
+
+  temp = "Done";
+
+  if (STOPPED (j) && format == 0)
+    {
+      if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0))
+       temp = "Stopped";
+      else
+       {
+         temp = retcode_name_buffer;
+         sprintf (temp, "Stopped(%s)", signal_name (WSTOPSIG (p->status)));
+       }
+    }
+  else if (RUNNING (j))
+    temp = "Running";
+  else
+    {
+      if (WIFSTOPPED (p->status))
+       temp = strsignal (WSTOPSIG (p->status));
+      else if (WIFSIGNALED (p->status))
+       temp = strsignal (WTERMSIG (p->status));
+      else if (WIFEXITED (p->status))
+       {
+         temp = retcode_name_buffer;
+         es = WEXITSTATUS (p->status);
+         if (es == 0)
+           strcpy (temp, "Done");
+         else if (posixly_correct)
+           sprintf (temp, "Done(%d)", es);
+         else
+           sprintf (temp, "Exit %d", es);
+       }
+      else
+       temp = "Unknown status";
+    }
+
+  return temp;
+}
+
 /* This is the way to print out information on a job if you
    know the index.  FORMAT is:
 
@@ -893,7 +964,7 @@ print_pipeline (p, job_index, format, stream)
 {
   PROCESS *first, *last, *show;
   int es, name_padding;
-  char retcode_name_buffer[20], *temp;
+  char *temp;
 
   if (p == 0)
     return;
@@ -908,41 +979,14 @@ print_pipeline (p, job_index, format, stream)
        fprintf (stream, format ? "     " : " |");
 
       if (format != JLIST_STANDARD)
-       fprintf (stream, "%5d", (int)p->pid);
+       fprintf (stream, "%5ld", (long)p->pid);
 
       fprintf (stream, " ");
 
       if (format > -1 && job_index >= 0)
        {
          show = format ? p : last;
-         temp = "Done";
-
-         if (STOPPED (job_index) && format == 0)
-           temp = "Stopped";
-
-         else if (RUNNING (job_index))
-           temp = "Running";
-         else
-           {
-             if (WIFSTOPPED (show->status))
-               temp = (char *)strsignal (WSTOPSIG (show->status));
-             else if (WIFSIGNALED (show->status))
-               temp = (char *)strsignal (WTERMSIG (show->status));
-             else if (WIFEXITED (show->status))
-               {
-                 temp = retcode_name_buffer;
-                 es = WEXITSTATUS (show->status);
-
-                 if (es == 0)
-                   strcpy (temp, "Done");
-                 else if (posixly_correct)
-                   sprintf (temp, "Done(%d)", es);
-                 else
-                   sprintf (temp, "Exit %d", es);
-               }
-             else
-               temp = "Unknown status";
-           }
+         temp = printable_job_status (job_index, show, format);
 
          if (p != first)
            {
@@ -962,12 +1006,14 @@ print_pipeline (p, job_index, format, stream)
 
              es = STRLEN (temp);
              if (es == 0)
-               es = 2; /* strlen ("| ") */
+               es = 2; /* strlen ("| ") */
              name_padding = LONGEST_SIGNAL_DESC - es;
 
              fprintf (stream, "%*s", name_padding, "");
 
-             if ((WIFSTOPPED (show->status) == 0) && WIFCORED (show->status))
+             if ((WIFSTOPPED (show->status) == 0) &&
+                 (WIFCONTINUED (show->status) == 0) &&
+                 WIFCORED (show->status))
                fprintf (stream, "(core dumped) ");
            }
        }
@@ -1023,7 +1069,7 @@ pretty_print_job (job_index, format, stream)
   /* Format only pid information about the process group leader? */
   if (format == JLIST_PID_ONLY)
     {
-      fprintf (stream, "%d\n", (int)jobs[job_index]->pipe->pid);
+      fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid);
       UNBLOCK_CHILD (oset);
       return;
     }
@@ -1041,7 +1087,7 @@ pretty_print_job (job_index, format, stream)
   if (format != JLIST_NONINTERACTIVE)
     fprintf (stream, "[%d]%c ", job_index + 1,
              (job_index == current_job) ? '+':
-               (job_index == previous_job) ? '-' : ' ');
+               (job_index == previous_job) ? '-' : ' ');
 
   if (format == JLIST_NONINTERACTIVE)
     format = JLIST_LONG;
@@ -1150,9 +1196,9 @@ make_child (command, async_p)
     {
       /* In the child.  Give this child the right process group, set the
         signals to the default state for a new process. */
-      pid_t mine;
+      pid_t mypid;
 
-      mine = getpid ();
+      mypid = getpid ();
 #if defined (BUFFERED_INPUT)
       /* Close default_buffered_input if it's > 0.  We don't close it if it's
         0 because that's the file descriptor used when redirecting input,
@@ -1169,7 +1215,7 @@ make_child (command, async_p)
             process group. */
 
          if (pipeline_pgrp == 0)       /* This is the first child. */
-           pipeline_pgrp = mine;
+           pipeline_pgrp = mypid;
 
          /* Check for running command in backquotes. */
          if (pipeline_pgrp == shell_pgrp)
@@ -1185,14 +1231,20 @@ make_child (command, async_p)
             this would have for the first child) is an error.  Section
             B.4.3.3, p. 237 also covers this, in the context of job control
             shells. */
-         if (setpgid (mine, pipeline_pgrp) < 0)
-           sys_error ("child setpgid (%d to %d)", mine, pipeline_pgrp);
+         if (setpgid (mypid, pipeline_pgrp) < 0)
+           sys_error ("child setpgid (%ld to %ld)", (long)mypid, (long)pipeline_pgrp);
 #if defined (PGRP_PIPE)
-         if (pipeline_pgrp == mine)
+         if (pipeline_pgrp == mypid)
            {
 #endif
+             /* By convention (and assumption above), if
+                pipeline_pgrp == shell_pgrp, we are making a child for
+                command substitution.
+                In this case, we don't want to give the terminal to the
+                shell's process group (we could be in the middle of a
+                pipeline, for example). */
              if (async_p == 0 && pipeline_pgrp != shell_pgrp)
-               give_terminal_to (pipeline_pgrp);
+               give_terminal_to (pipeline_pgrp, 0);
 
 #if defined (PGRP_PIPE)
              pipe_read (pgrp_pipe);
@@ -1235,7 +1287,7 @@ make_child (command, async_p)
              pipeline_pgrp = pid;
              /* Don't twiddle terminal pgrps in the parent!  This is the bug,
                 not the good thing of twiddling them in the child! */
-             /* give_terminal_to (pipeline_pgrp); */
+             /* give_terminal_to (pipeline_pgrp, 0); */
            }
          /* This is done on the recommendation of the Rationale section of
             the POSIX 1003.1 standard, where it discusses job control and
@@ -1285,20 +1337,13 @@ default_tty_job_signals ()
    state kept in here.  When a job ends normally, we set the state in here
    to the state of the tty. */
 
+static TTYSTRUCT shell_tty_info;
+
 #if defined (NEW_TTY_DRIVER)
-static struct sgttyb shell_tty_info;
 static struct tchars shell_tchars;
 static struct ltchars shell_ltchars;
 #endif /* NEW_TTY_DRIVER */
 
-#if defined (TERMIO_TTY_DRIVER)
-static struct termio shell_tty_info;
-#endif /* TERMIO_TTY_DRIVER */
-
-#if defined (TERMIOS_TTY_DRIVER)
-static struct termios shell_tty_info;
-#endif /* TERMIOS_TTY_DRIVER */
-
 #if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT)
 /* Since the BSD tty driver does not allow us to change the tty modes
    while simultaneously waiting for output to drain and preserving
@@ -1371,7 +1416,7 @@ get_tty_state ()
          /* Only print an error message if we're really interactive at
             this time. */
          if (interactive)
-           sys_error ("[%d: %d] tcgetattr", getpid (), shell_level);
+           sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level);
 #endif
          return -1;
        }
@@ -1410,7 +1455,7 @@ set_tty_state ()
          /* Only print an error message if we're really interactive at
             this time. */
          if (interactive)
-           sys_error ("[%d: %d] tcsetattr", getpid (), shell_level);
+           sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level);
          return -1;
        }
 #endif /* TERMIOS_TTY_DRIVER */
@@ -1450,7 +1495,9 @@ last_pid (job)
 
 /* Wait for a particular child of the shell to finish executing.
    This low-level function prints an error message if PID is not
-   a child of this shell.  It returns -1 if it fails, or 0 if not. */
+   a child of this shell.  It returns -1 if it fails, or 0 if not
+   (whatever wait_for returns).  If the child is not found in the
+   jobs table, it returns 127. */
 int
 wait_for_single_pid (pid)
      pid_t pid;
@@ -1465,22 +1512,19 @@ wait_for_single_pid (pid)
 
   if (child == 0)
     {
-      internal_error ("wait: pid %d is not a child of this shell", pid);
+      internal_error ("wait: pid %ld is not a child of this shell", (long)pid);
       return (127);
     }
 
   r = wait_for (pid);
 
-  /* POSIX.2: if we just waited for $!, we can remove the job from the
-     jobs table. */
-  if (pid == last_asynchronous_pid)
-    {
-      BLOCK_CHILD (set, oset);
-      job = find_job (pid);
-      if (job != NO_JOB && jobs[job] && DEADJOB (job))
-       jobs[job]->flags |= J_NOTIFIED;
-      UNBLOCK_CHILD (oset);
-    }
+  /* POSIX.2: if we just waited for a job, we can remove it from the jobs
+     table. */
+  BLOCK_CHILD (set, oset);
+  job = find_job (pid);
+  if (job != NO_JOB && jobs[job] && DEADJOB (job))
+    jobs[job]->flags |= J_NOTIFIED;
+  UNBLOCK_CHILD (oset);
 
   return r;
 }
@@ -1489,11 +1533,11 @@ wait_for_single_pid (pid)
 void
 wait_for_background_pids ()
 {
-  register int i, count;
+  register int i, count, r, waited_for;
   sigset_t set, oset;
   pid_t pid;
 
-  for (;;)
+  for (waited_for = 0;;)
     {
       BLOCK_CHILD (set, oset);
 
@@ -1517,10 +1561,23 @@ wait_for_background_pids ()
            pid = last_pid (i);
            UNBLOCK_CHILD (oset);
            QUIT;
-           wait_for_single_pid (pid);
+           errno = 0;          /* XXX */
+           r = wait_for_single_pid (pid);
+           if (r == -1)
+             {
+               if (errno == ECHILD)
+                 mark_all_jobs_as_dead ();
+             }
+           else
+             waited_for++;
            break;
          }
     }
+
+  /* POSIX.2 says the shell can discard the statuses of all completed jobs if
+     `wait' is called with no arguments. */
+  mark_dead_jobs_as_notified (1);
+  cleanup_dead_jobs ();
 }
 
 /* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */
@@ -1546,11 +1603,24 @@ static sighandler
 wait_sigint_handler (sig)
      int sig;
 {
+  SigHandler *sigint_handler;
+
   if (interrupt_immediately ||
       (this_shell_builtin && this_shell_builtin == wait_builtin))
     {
       last_command_exit_value = EXECUTION_FAILURE;
       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). */
+      if (this_shell_builtin && this_shell_builtin == wait_builtin &&
+         signal_is_trapped (SIGINT) &&
+         ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler))
+       {
+         interrupt_immediately = 0;
+         trap_handler (SIGINT);        /* set pending_traps[SIGINT] */
+         longjmp (wait_intr_buf, 1);
+       }
+      
       ADDINTERRUPT;
       QUIT;
     }
@@ -1576,44 +1646,56 @@ process_exit_status (status)
     return (EXECUTION_SUCCESS);
 }
 
-static int
-job_exit_status (job)
+/* Return the exit status of the last process in the pipeline for job JOB.
+   This is the exit status of the entire job. */
+static WAIT
+raw_job_exit_status (job)
      int job;
 {
   register PROCESS *p;
   for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
     ;
-  return (process_exit_status (p->status));
+  return (p->status);
+}
+
+/* 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
+job_exit_status (job)
+     int job;
+{
+  return (process_exit_status (raw_job_exit_status (job)));
 }
 
-/* Wait for pid (one of our children) to terminate, then
-   return the termination state. */
 #define FIND_CHILD(pid, child) \
   do \
     { \
       child = find_pipeline (pid); \
       if (child == 0) \
        { \
-         give_terminal_to (shell_pgrp); \
+         give_terminal_to (shell_pgrp, 0); \
          UNBLOCK_CHILD (oset); \
-         internal_error ("wait_for: No record of process %d", pid); \
+         internal_error ("wait_for: No record of process %ld", (long)pid); \
          restore_sigint_handler (); \
          return (termination_state = 127); \
        } \
     } \
   while (0)
 
+/* Wait for pid (one of our children) to terminate, then
+   return the termination state.  Returns 127 if PID is not found in
+   the jobs table.  Returns -1 if waitchld() returns -1, indicating
+   that there are no unwaited-for child processes. */
 int
 wait_for (pid)
      pid_t pid;
 {
-  int job, termination_state;
+  int job, termination_state, r;
+  WAIT s;
   register PROCESS *child;
   sigset_t set, oset;
-#if 0
   register PROCESS *p;
-  int job_state, any_stopped;
-#endif
 
   /* 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
@@ -1645,33 +1727,15 @@ wait_for (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.
-         We check for JDEAD in case the job state has been set by waitchld
-         after receipt of a SIGCHLD. */
+        job to finish.  Otherwise, we are waiting for the child to finish.
+        We check for JDEAD in case the job state has been set by waitchld
+        after receipt of a SIGCHLD. */
       if (job == NO_JOB)
        job = find_job (pid);
 
-#if 0
-      /* XXX - let waitchld take care of setting this.  If the job has
-        already exited before this is called, sigchld_handler will have
-        called waitchld and this will be set to JDEAD. */
-      if (job != NO_JOB && JOBSTATE (job) != JDEAD)
-       {
-         job_state = any_stopped = 0;
-         p = jobs[job]->pipe;
-         do
-           {
-             job_state |= p->running;
-             if (p->running == 0)
-               any_stopped |= WIFSTOPPED (p->status);
-             p = p->next;
-           }
-         while (p != jobs[job]->pipe);
-
-         if (job_state == 0)
-           jobs[job]->state = any_stopped ? JSTOPPED : JDEAD;
-       }
-#endif
+      /* waitchld() takes care of setting the state of the job.  If the job
+        has already exited before this is called, sigchld_handler will have
+        called waitchld and the state will be set to JDEAD. */
 
       if (child->running || (job != NO_JOB && RUNNING (job)))
        {
@@ -1694,12 +1758,29 @@ wait_for (pid)
          sigaction (SIGCHLD, &act, &oact);
 #  endif
          waiting_for_job = 1;
-         waitchld (pid, 1);
+         r = waitchld (pid, 1);
 #  if defined (MUST_UNBLOCK_CHLD)
          sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
          sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
 #  endif
          waiting_for_job = 0;
+         if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
+           {
+             termination_state = -1;
+             goto wait_for_return;
+           }
+
+         /* If child is marked as running, but waitpid() returns -1/ECHILD,
+            there is something wrong.  Somewhere, wait should have returned
+            that child's pid.  Mark the child as not running and the job,
+            if it exists, as JDEAD. */
+         if (r == -1 && errno == ECHILD)
+           {
+             child->running = 0;
+             child->status = 0;        /* XXX */
+             if (job != NO_JOB)
+               jobs[job]->state = JDEAD;
+           }
 #endif /* WAITPID_BROKEN */
        }
 
@@ -1715,14 +1796,33 @@ wait_for (pid)
   /* 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 (job != NO_JOB)
     termination_state = job_exit_status (job);
   else
     termination_state = process_exit_status (child->status);
 
   if (job == NO_JOB || IS_JOBCONTROL (job))
-    give_terminal_to (shell_pgrp);
+    {
+      /* XXX - under what circumstances is a job not present in the jobs
+        table (job == NO_JOB)?
+               1.  command substitution
+
+        In the case of command substitution, at least, it's probably not
+        the right thing to give the terminal to the shell's process group,
+        even though there is code in subst.c:command_substitute to work
+        around it.
+
+        Things that don't:
+               $PROMPT_COMMAND execution
+               process substitution
+       */
+#if 0
+if (job == NO_JOB)
+  itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp);
+#endif
+
+      give_terminal_to (shell_pgrp, 0);
+    }
 
   /* If the command did not exit cleanly, or the job is just
      being stopped, then reset the tty state back to what it
@@ -1733,14 +1833,33 @@ wait_for (pid)
     {
       if (interactive_shell && subshell_environment == 0)
        {
-         if (WIFSIGNALED (child->status) || WIFSTOPPED (child->status))
+         /* This used to use `child->status'.  That's wrong, however, for
+            pipelines.  `child' is the first process in the pipeline.  It's
+            likely that the process we want to check for abnormal termination
+            or stopping is the last process in the pipeline, especially if
+            it's long-lived and the first process is short-lived.  Since we
+            know we have a job here, we can check all the processes in this
+            job's pipeline and see if one of them stopped or terminated due
+            to a signal.  We might want to change this later to just check
+            the last process in the pipeline.  If no process exits due to a
+            signal, S is left as the status of the last job in the pipeline. */
+         p = jobs[job]->pipe;
+         do
+           {
+             s = p->status;
+             if (WIFSIGNALED(s) || WIFSTOPPED(s))
+               break;
+             p = p->next;
+           }
+         while (p != jobs[job]->pipe);
+
+         if (WIFSIGNALED (s) || WIFSTOPPED (s))
            {
              set_tty_state ();
-             /* If the foreground job was suspended with ^Z (SIGTSTP), and
-                the user has requested it, get a new window size. */
-             if (check_window_size && WIFSTOPPED (child->status) &&
-                   (WSTOPSIG (child->status) == SIGTSTP) &&
-                   job == current_job)
+
+             /* If the current job was stopped or killed by a signal, and
+                the user has requested it, get a possibly new window size */
+             if (check_window_size && job == current_job)
                get_new_window_size (0);
            }
          else
@@ -1751,8 +1870,7 @@ wait_for (pid)
             by SIGINT, then print a newline to compensate for the kernel
             printing the ^C without a trailing newline. */
          if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) &&
-               WIFSIGNALED (child->status) &&
-               WTERMSIG (child->status) == SIGINT)
+               WIFSIGNALED (s) && WTERMSIG (s) == SIGINT)
            {
              /* If SIGINT is not trapped and the shell is in a for, while,
                 or until loop, act as if the shell received SIGINT as
@@ -1766,25 +1884,18 @@ wait_for (pid)
                  fflush (stdout);
                }
            }
-
-         notify_and_cleanup ();
-       }
-      else
-       {
-         /* If this job is dead, and the shell is not interactive, make
-            sure we turn on the notify bit so we don't get an unwanted
-            message about the job's termination, and so delete_job really
-            clears the slot in the jobs table. */
-#if 0
-         if (DEADJOB (job))
-           jobs[job]->flags |= J_NOTIFIED;
-         cleanup_dead_jobs ();
-#else
-         notify_and_cleanup ();
-#endif
        }
+
+      /* If this job is dead, notify the user of the status.  If the shell
+        is interactive, this will display a message on the terminal.  If
+        the shell is not interactive, make sure we turn on the notify bit
+        so we don't get an unwanted message about the job's termination,
+        and so delete_job really clears the slot in the jobs table. */
+      notify_and_cleanup ();
     }
 
+wait_for_return:
+
   UNBLOCK_CHILD (oset);
 
   /* Restore the original SIGINT signal handler before we return. */
@@ -1793,7 +1904,9 @@ wait_for (pid)
   return (termination_state);
 }
 
-/* Wait for the last process in the pipeline for JOB. */
+/* Wait for the last process in the pipeline for JOB.  Returns whatever
+   wait_for returns: the last process's termination state or -1 if there
+   are no unwaited-for child processes or an error occurs. */
 int
 wait_for_job (job)
      int job;
@@ -1810,15 +1923,12 @@ wait_for_job (job)
   pid = last_pid (job);
   r = wait_for (pid);
 
-  /* POSIX.2: if we just waited for $!, we can remove the job from the
-     jobs table. */
-  if (pid == last_asynchronous_pid)
-    {
-      BLOCK_CHILD (set, oset);
-      if (job != NO_JOB && jobs[job] && DEADJOB (job))
-       jobs[job]->flags |= J_NOTIFIED;
-      UNBLOCK_CHILD (oset);
-    }
+  /* POSIX.2: we can remove the job from the jobs table if we just waited
+     for it. */
+  BLOCK_CHILD (set, oset);
+  if (job != NO_JOB && jobs[job] && DEADJOB (job))
+    jobs[job]->flags |= J_NOTIFIED;
+  UNBLOCK_CHILD (oset);
 
   return r;
 }
@@ -1833,7 +1943,7 @@ notify_and_cleanup ()
   if (jobs_list_frozen)
     return;
 
-  if (interactive || interactive_shell == 0)
+  if (interactive || interactive_shell == 0 || sourcelevel)
     notify_of_job_status ();
 
   cleanup_dead_jobs ();
@@ -1844,7 +1954,7 @@ notify_and_cleanup ()
 void
 reap_dead_jobs ()
 {
-  mark_dead_jobs_as_notified ();
+  mark_dead_jobs_as_notified (0);
   cleanup_dead_jobs ();
 }
 
@@ -1876,7 +1986,7 @@ most_recent_job_in_state (job, state)
 /* Return the newest *stopped* job older than JOB, or NO_JOB if not
    found. */
 static int
-last_stopped_job (job)
+job_last_stopped (job)
      int job;
 {
   return (most_recent_job_in_state (job, JSTOPPED));
@@ -1885,7 +1995,7 @@ last_stopped_job (job)
 /* Return the newest *running* job older than JOB, or NO_JOB if not
    found. */
 static int
-last_running_job (job)
+job_last_running (job)
      int job;
 {
   return (most_recent_job_in_state (job, JRUNNING));
@@ -1917,7 +2027,7 @@ set_current_job (job)
   candidate = NO_JOB;
   if (STOPPED (current_job))
     {
-      candidate = last_stopped_job (current_job);
+      candidate = job_last_stopped (current_job);
 
       if (candidate != NO_JOB)
        {
@@ -1933,8 +2043,8 @@ set_current_job (job)
      alternative to use based on whether or not JOBSTATE(current_job) is
      JSTOPPED. */
 
-  candidate = RUNNING (current_job) ? last_running_job (current_job)
-                                   : last_running_job (job_slots);
+  candidate = RUNNING (current_job) ? job_last_running (current_job)
+                                   : job_last_running (job_slots);
 
   if (candidate != NO_JOB)
     {
@@ -1971,11 +2081,11 @@ reset_current ()
 
       /* Second choice: the most recently stopped job. */
       if (candidate == NO_JOB)
-       candidate = last_stopped_job (job_slots);
+       candidate = job_last_stopped (job_slots);
 
       /* Third choice: the newest running job. */
       if (candidate == NO_JOB)
-       candidate = last_running_job (job_slots);
+       candidate = job_last_running (job_slots);
     }
 
   /* If we found a job to use, then use it.  Otherwise, there
@@ -2021,15 +2131,7 @@ start_job (job, foreground)
   int already_running;
   sigset_t set, oset;
   char *wd;
-#if defined (NEW_TTY_DRIVER)
-  static struct sgttyb save_stty;
-#endif
-#if defined (TERMIO_TTY_DRIVER)
-  static struct termio save_stty;
-#endif
-#if defined (TERMIOS_TTY_DRIVER)
-  static struct termios save_stty;
-#endif
+  static TTYSTRUCT save_stty;
 
   BLOCK_CHILD (set, oset);
 
@@ -2095,7 +2197,7 @@ start_job (job, foreground)
       save_stty = shell_tty_info;
       /* Give the terminal to this job. */
       if (IS_JOBCONTROL (job))
-       give_terminal_to (jobs[job]->pgrp);
+       give_terminal_to (jobs[job]->pgrp, 0);
     }
   else
     jobs[job]->flags &= ~J_FOREGROUND;
@@ -2142,13 +2244,13 @@ kill_pid (pid, sig, group)
   int job, result;
   sigset_t set, oset;
 
-  BLOCK_CHILD (set, oset);
-  p = find_pipeline (pid);
-  job = find_job (pid);
-
   result = EXECUTION_SUCCESS;
   if (group)
     {
+      BLOCK_CHILD (set, oset);
+      p = find_pipeline (pid);
+      job = find_job (pid);
+
       if (job != NO_JOB)
        {
          jobs[job]->flags &= ~J_NOTIFIED;
@@ -2184,11 +2286,12 @@ kill_pid (pid, sig, group)
        }
       else
        result = killpg (pid, sig);
+
+      UNBLOCK_CHILD (oset);
     }
   else
     result = kill (pid, sig);
 
-  UNBLOCK_CHILD (oset);
   return (result);
 }
 
@@ -2198,20 +2301,25 @@ static sighandler
 sigchld_handler (sig)
      int sig;
 {
-  int n;
+  int n, oerrno;
 
+  oerrno = errno;
   REINSTALL_SIGCHLD_HANDLER;
   sigchld++;
   n = 0;
   if (waiting_for_job == 0)
     n = waitchld (-1, 0);
+  errno = oerrno;
   SIGRETURN (n);
 }
 
 /* waitchld() reaps dead or stopped children.  It's called by wait_for and
-   flush_child, and runs until there aren't any children terminating any more.
+   sigchld_handler, and runs until there aren't any children terminating any
+   more.
    If BLOCK is 1, this is to be a blocking wait for a single child, although
-   an arriving SIGCHLD could cause the wait to be non-blocking. */
+   an arriving SIGCHLD could cause the wait to be non-blocking.  It returns
+   the number of children reaped, or -1 if there are no unwaited-for child
+   processes. */
 static int
 waitchld (wpid, block)
      pid_t wpid;
@@ -2220,8 +2328,7 @@ waitchld (wpid, block)
   WAIT status;
   PROCESS *child;
   pid_t pid;
-  int call_set_current, last_stopped_job, job, children_exited;
-  int job_state, any_stopped, any_tstped, waitpid_flags, tstatus;
+  int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
 
   call_set_current = children_exited = 0;
   last_stopped_job = NO_JOB;
@@ -2229,23 +2336,38 @@ waitchld (wpid, block)
   do
     {
       /* We don't want to be notified about jobs stopping if job control
-         is not active.  XXX - was interactive_shell instead of job_control */
+        is not active.  XXX - was interactive_shell instead of job_control */
       waitpid_flags = (job_control && subshell_environment == 0)
-                       ? WUNTRACED
+                       ? (WUNTRACED|WCONTINUED)
                        : 0;
       if (sigchld || block == 0)
        waitpid_flags |= WNOHANG;
       pid = WAITPID (-1, &status, waitpid_flags);
+
       /* The check for WNOHANG is to make sure we decrement sigchld only
         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)
+       {
+         if (children_exited == 0)
+           return -1;
+         else
+           break;
+       }
 
-      /* If waitpid returns 0, there are running children. */
+      /* If waitpid returns 0, there are running children.  If it returns -1,
+        the only other error POSIX says it can return is EINTR. */
       if (pid <= 0)
        continue;       /* jumps right to the test */
 
-      children_exited++;
+      /* 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)
+       children_exited++;
 
       /* Locate our PROCESS for this pid. */
       child = find_pipeline (pid);
@@ -2260,207 +2382,282 @@ waitchld (wpid, block)
       while (child->pid != pid)
        child = child->next;
 
-      /* Remember status, and fact that process is not running. */
+      /* Remember status, and whether or not the process is running. */
       child->status = status;
-      child->running = 0;
+      child->running = WIFCONTINUED(status) ? 1 : 0;
 
       job = find_job (pid);
   
       if (job == NO_JOB)
-        continue;
+       continue;
 
-      /* Note that we're resetting `child' here because we now want to
-        deal with the job. */
-      child = jobs[job]->pipe;
-      jobs[job]->flags &= ~J_NOTIFIED;
+      call_set_current += set_job_status_and_cleanup (job);
 
-      /* If all children are not running, but any of them is
-        stopped, then the job is stopped, not dead. */
-      job_state = any_stopped = any_tstped = 0;
-      do
+      if (STOPPED (job))
+       last_stopped_job = job;
+      else if (DEADJOB (job) && last_stopped_job == job)
+       last_stopped_job = NO_JOB;
+    }
+  while ((sigchld || block == 0) && pid > (pid_t)0);
+
+  /* If a job was running and became stopped, then set the current
+     job.  Otherwise, don't change a thing. */
+  if (call_set_current)
+    {
+      if (last_stopped_job != NO_JOB)
+       set_current_job (last_stopped_job);
+      else
+       reset_current ();
+    }
+
+  /* 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);
+
+  /* We have successfully recorded the useful information about this process
+     that has just changed state.  If we notify asynchronously, and the job
+     that this process belongs to is no longer running, then notify the user
+     of that fact now. */
+  if (asynchronous_notification && interactive)
+    notify_of_job_status ();
+
+  return (children_exited);
+}
+
+/* Set the status of JOB and perform any necessary cleanup if the job is
+   marked as JDEAD.
+
+   Currently, the cleanup activity is restricted to handling any SIGINT
+   received while waiting for a foreground job to finish. */
+static int
+set_job_status_and_cleanup (job)
+     int job;
+{
+  PROCESS *child;
+  int tstatus, job_state, any_stopped, any_tstped, call_set_current;
+  SigHandler *temp_handler;
+
+  child = jobs[job]->pipe;
+  jobs[job]->flags &= ~J_NOTIFIED;
+
+  call_set_current = 0;
+
+  /*
+   * COMPUTE JOB STATUS
+   */
+
+  /* If all children are not running, but any of them is  stopped, then
+     the job is stopped, not dead. */
+  job_state = any_stopped = any_tstped = 0;
+  do
+    {
+      job_state |= child->running;
+      if (child->running == 0 && (WIFSTOPPED (child->status)))
        {
-         job_state |= child->running;
-         if (child->running == 0 && (WIFSTOPPED (child->status)))
-           {
-             any_stopped = 1;
-             any_tstped |= interactive && job_control &&
+         any_stopped = 1;
+         any_tstped |= interactive && job_control &&
                            (WSTOPSIG (child->status) == SIGTSTP);
-           }
-         child = child->next;
        }
-      while (child != jobs[job]->pipe);
+      child = child->next;
+    }
+  while (child != jobs[job]->pipe);
+
+  /* If job_state != 0, the job is still running, so don't bother with
+     setting the process exit status and job state unless we're
+     transitioning from stopped to running. */
+  if (job_state != 0 && JOBSTATE(job) != JSTOPPED)
+    return 0;
+
+  /*
+   * SET JOB STATUS
+   */
+
+  /* The job is either stopped or dead.  Set the state of the job accordingly. */
+  if (any_stopped)
+    {
+      jobs[job]->state = JSTOPPED;
+      jobs[job]->flags &= ~J_FOREGROUND;
+      call_set_current++;
+      /* Suspending a job with SIGTSTP breaks all active loops. */
+      if (any_tstped && loop_level)
+       breaking = loop_level;
+    }
+  else if (job_state != 0)     /* was stopped, now running */
+    {
+      jobs[job]->state = JRUNNING;
+      call_set_current++;
+    }
+  else
+    {
+      jobs[job]->state = JDEAD;
 
-      /* If job_state != 0, the job is still running, so don't bother with
-        setting the process exit status and job state. */
-      if (job_state != 0)
-        continue;
+      if (IS_FOREGROUND (job))
+       setjstatus (job);
 
-      /* The job is either stopped or dead.  Set the state of the job
-        accordingly. */
-      if (any_stopped)
+      /* If this job has a cleanup function associated with it, call it
+        with `cleanarg' as the single argument, then set the function
+        pointer to NULL so it is not inadvertently called twice.  The
+        cleanup function is responsible for deallocating cleanarg. */
+      if (jobs[job]->j_cleanup)
        {
-         jobs[job]->state = JSTOPPED;
-         jobs[job]->flags &= ~J_FOREGROUND;
-         call_set_current++;
-         last_stopped_job = job;
-         /* Suspending a job with SIGTSTP breaks all active loops. */
-         if (any_tstped && loop_level)
-           breaking = loop_level;
+         (*jobs[job]->j_cleanup) (jobs[job]->cleanarg);
+         jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL;
        }
-      else
+    }
+
+  /*
+   * CLEANUP
+   *
+   * Currently, we just do special things if we got a SIGINT while waiting
+   * for a foreground job to complete
+   */
+
+  if (jobs[job]->state == JDEAD)
+    {
+      /* If we're running a shell script and we get a SIGINT with a
+        SIGINT trap handler, but the foreground job handles it and
+        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) &&
+         signal_is_trapped (SIGINT))
        {
-         /* ASSERT(child == jobs[job]->pipe); */
-         jobs[job]->state = JDEAD;
-         if (job == last_stopped_job)
-           last_stopped_job = NO_JOB;
-
-         if (IS_FOREGROUND (job))
-           setjstatus (job);           /* XXX */
-
-         /* If this job has a cleanup function associated with it, call it
-            with `cleanarg' as the single argument, then set the function
-            pointer to NULL so it is not inadvertently called twice.  The
-            cleanup function is responsible for deallocating cleanarg. */
-         if (jobs[job]->j_cleanup)
-           {
-             (*jobs[job]->j_cleanup) (jobs[job]->cleanarg);
-             jobs[job]->j_cleanup = (VFunction *)NULL;
-           }
+         wait_sigint_received = 0;
+         last_command_exit_value = process_exit_status (child->status);
 
-         /* XXX
-            If we're running a shell script and we get a SIGINT with a
-            SIGINT trap handler, but the foreground job handles it and
-            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) &&
-             signal_is_trapped (SIGINT))
-           {
-             wait_sigint_received = 0;
-             last_command_exit_value = process_exit_status (child->status);
+         jobs_list_frozen = 1;
+         tstatus = maybe_call_trap_handler (SIGINT);
+         jobs_list_frozen = 0;
+       }
 
-             jobs_list_frozen = 1;
-             tstatus = maybe_call_trap_handler (SIGINT);
-             jobs_list_frozen = 0;
-           }
+      /* If the foreground job is killed by SIGINT when job control is not
+        active, we need to perform some special handling.
 
-         /* If the foreground job is killed by SIGINT when
-            job control is not active, we need to perform
-            some special handling.
-
-            The check of wait_sigint_received is a way to
-            determine if the SIGINT came from the keyboard
-            (in which case the shell has already seen it,
-            and wait_sigint_received is non-zero, because
-            keyboard 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) &&
+        The check of wait_sigint_received is a way to determine if the
+        SIGINT came from the keyboard (in which case the shell has already
+        seen it, and wait_sigint_received is non-zero, because keyboard
+        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) &&
              IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
+       {
+         wait_sigint_received = 0;
+
+         /* If SIGINT is trapped, set the exit status so that the trap
+            handler can see it. */
+         if (signal_is_trapped (SIGINT))
+           last_command_exit_value = process_exit_status (child->status);
+
+         /* If the signal is trapped, let the trap handler get it no matter
+            what and simply return if the trap handler returns.
+           maybe_call_trap_handler() may cause dead jobs to be removed from
+           the job table because of a call to execute_command.  We work
+           around this by setting JOBS_LIST_FROZEN. */
+         jobs_list_frozen = 1;
+         tstatus = maybe_call_trap_handler (SIGINT);
+         jobs_list_frozen = 0;
+         if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
            {
-             wait_sigint_received = 0;
-
-             /* If SIGINT is trapped, set the exit status so
-                that the trap handler can see it. */
-             if (signal_is_trapped (SIGINT))
-               last_command_exit_value = process_exit_status (child->status);
-
-             /* If the signal is trapped, let the trap handler
-                get it no matter what and simply return if
-                the trap handler returns.
-                maybe_call_trap_handler() may cause dead jobs
-                to be removed from the job table because of
-                a call to execute_command.  Watch out for this. */
-             jobs_list_frozen = 1;
-             tstatus = maybe_call_trap_handler (SIGINT);
-             jobs_list_frozen = 0;
-             if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
-               {
-                 /* wait_sigint_handler () has already seen SIGINT and
-                    allowed the wait builtin to jump out.  We need to
-                    call the original SIGINT handler, if necessary.  If
-                    the original handler is SIG_DFL, we need to resend
-                    the signal to ourselves. */
-                 SigHandler *temp_handler;
-
-                 temp_handler = old_sigint_handler;
-                 /* Bogus.  If we've reset the signal handler as the result
-                    of a trap caught on SIGINT, then old_sigint_handler
-                    will point to trap_handler, which now knows nothing about
-                    SIGINT (if we reset the sighandler to the default).
-                    In this case, we have to fix things up.  What a crock. */
-                 if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0)
-                   temp_handler = trap_to_sighandler (SIGINT);
-                 restore_sigint_handler ();
-                 if (temp_handler == SIG_DFL)
-                   termination_unwind_protect (SIGINT);
-                 else if (temp_handler != SIG_IGN)
-                   (*temp_handler) (SIGINT);
-               }
+             /* wait_sigint_handler () has already seen SIGINT and
+                allowed the wait builtin to jump out.  We need to
+                call the original SIGINT handler, if necessary.  If
+                the original handler is SIG_DFL, we need to resend
+                the signal to ourselves. */
+
+             temp_handler = old_sigint_handler;
+
+             /* Bogus.  If we've reset the signal handler as the result
+                of a trap caught on SIGINT, then old_sigint_handler
+                will point to trap_handler, which now knows nothing about
+                SIGINT (if we reset the sighandler to the default).
+                In this case, we have to fix things up.  What a crock. */
+             if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0)
+                 temp_handler = trap_to_sighandler (SIGINT);
+               restore_sigint_handler ();
+               if (temp_handler == SIG_DFL)
+                 termination_unwind_protect (SIGINT);
+               else if (temp_handler != SIG_IGN)
+                 (*temp_handler) (SIGINT);
            }
        }
     }
-  while ((sigchld || block == 0) && pid > (pid_t)0);
 
-  /* If a job was running and became stopped, then set the current
-     job.  Otherwise, don't change a thing. */
-  if (call_set_current)
-    if (last_stopped_job != NO_JOB)
-      set_current_job (last_stopped_job);
-    else
-      reset_current ();
+  return call_set_current;
+}
 
-  /* Call a SIGCHLD trap handler for each child that exits, if one is set. */
-  if (job_control && signal_is_trapped (SIGCHLD) &&
-      trap_list[SIGCHLD] != (char *)IGNORE_SIG)
+/* Build the array of values for the $PIPESTATUS variable from the set of
+   exit statuses of all processes in the job J. */
+static void
+setjstatus (j)
+     int j;
+{
+#if defined (ARRAY_VARS)
+  register int i;
+  register PROCESS *p;
+
+  for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++)
+    ;
+  i++;
+  if (statsize <= i)
     {
-      char *trap_command;
-      int i;
+      pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int));
+      statsize = i;
+    }
+  i = 0;
+  p = jobs[j]->pipe;
+  do
+    {
+      pstatuses[i++] = process_exit_status (p->status);
+      p = p->next;
+    }
+  while (p != jobs[j]->pipe);
 
-      /* Turn off the trap list during the call to parse_and_execute ()
-        to avoid potentially infinite recursive calls.  Preserve the
-        values of last_command_exit_value, last_made_pid, and the_pipeline
-        around the execution of the trap commands. */
-      trap_command = savestring (trap_list[SIGCHLD]);
+  pstatuses[i] = -1;   /* sentinel */
+  set_pipestatus_array (pstatuses);
+#endif
+}
 
-      begin_unwind_frame ("SIGCHLD trap");
-      unwind_protect_int (last_command_exit_value);
-      if (sizeof (pid_t) == sizeof (short))
-       unwind_protect_short (last_made_pid);
-      else
-       unwind_protect_int (last_made_pid);
-      unwind_protect_int (interrupt_immediately);
-      unwind_protect_int (jobs_list_frozen);
-      unwind_protect_pointer (the_pipeline);
+static void
+run_sigchld_trap (nchild)
+     int nchild;
+{
+  char *trap_command;
+  int i;
 
-      /* 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 ()
-        to reference freed memory. */
-      add_unwind_protect ((Function *)xfree, trap_command);
-      add_unwind_protect ((Function *)maybe_set_sigchld_trap, trap_command);
+  /* Turn off the trap list during the call to parse_and_execute ()
+     to avoid potentially infinite recursive calls.  Preserve the
+     values of last_command_exit_value, last_made_pid, and the_pipeline
+     around the execution of the trap commands. */
+  trap_command = savestring (trap_list[SIGCHLD]);
 
-      the_pipeline = (PROCESS *)NULL;
-      restore_default_signal (SIGCHLD);
-      jobs_list_frozen = 1;
-      for (i = 0; i < children_exited; i++)
-       {
-         interrupt_immediately = 1;
-         parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST);
-       }
+  begin_unwind_frame ("SIGCHLD trap");
+  unwind_protect_int (last_command_exit_value);
+  unwind_protect_var (last_made_pid);
+  unwind_protect_int (interrupt_immediately);
+  unwind_protect_int (jobs_list_frozen);
+  unwind_protect_pointer (the_pipeline);
+  unwind_protect_pointer (subst_assign_varlist);
 
-      run_unwind_frame ("SIGCHLD trap");
-    }
+  /* 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 ()
+     to reference freed memory. */
+  add_unwind_protect ((Function *)xfree, trap_command);
+  add_unwind_protect ((Function *)maybe_set_sigchld_trap, trap_command);
 
-  /* We have successfully recorded the useful information about this process
-     that has just changed state.  If we notify asynchronously, and the job
-     that this process belongs to is no longer running, then notify the user
-     of that fact now. */
-  if (asynchronous_notification && interactive)
-    notify_of_job_status ();
+  subst_assign_varlist = (WORD_LIST *)NULL;
+  the_pipeline = (PROCESS *)NULL;
 
-  return (children_exited);
+  restore_default_signal (SIGCHLD);
+  jobs_list_frozen = 1;
+  for (i = 0; i < nchild; i++)
+    {
+      interrupt_immediately = 1;
+      parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST);
+    }
+
+  run_unwind_frame ("SIGCHLD trap");
 }
 
 /* Function to call when you want to notify people of changes
@@ -2475,6 +2672,9 @@ notify_of_job_status ()
   sigset_t set, oset;
   WAIT s;
 
+  if (jobs == 0 || job_slots == 0)
+    return;
+
   sigemptyset (&set);
   sigaddset (&set, SIGCHLD);
   sigaddset (&set, SIGTTOU);
@@ -2485,26 +2685,28 @@ notify_of_job_status ()
     {
       if (jobs[job] && IS_NOTIFIED (job) == 0)
        {
-         s = jobs[job]->pipe->status;
+         s = raw_job_exit_status (job);
          termsig = WTERMSIG (s);
 
+         /* POSIX.2 says we have to hang onto the statuses of at most the
+            last CHILD_MAX background processes if the shell is running a
+            script.  If the shell is not interactive, don't print anything
+            unless the job was killed by a signal. */
+         if (startup_state == 0 && WIFSIGNALED (s) == 0 &&
+               ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job)))
+           continue;
+         
          /* If job control is disabled, don't print the status messages.
             Mark dead jobs as notified so that they get cleaned up.  If
             startup_state == 2, we were started to run `-c command', so
-            don't print anything.  If the shell is not interactive, don't
-            print anything unless the job was killed by a signal. */
-         if ((job_control == 0 && interactive_shell) || startup_state == 2 ||
-               (startup_state == 0 && WIFSIGNALED (s) == 0))
+            don't print anything. */
+         if ((job_control == 0 && interactive_shell) || startup_state == 2)
            {
-#if 0
-             if (DEADJOB (job))
-#else
              /* POSIX.2 compatibility:  if the shell is not interactive,
                 hang onto the job corresponding to the last asynchronous
                 pid until the user has been notified of its status or does
                 a `wait'. */
              if (DEADJOB (job) && (interactive_shell || (find_last_pid (job) != last_asynchronous_pid)))
-#endif
                jobs[job]->flags |= J_NOTIFIED;
              continue;
            }
@@ -2516,14 +2718,12 @@ notify_of_job_status ()
            {
            case JDEAD:
              if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
-#if 1
                  termsig != SIGINT &&
-#endif
 #if defined (DONT_REPORT_SIGPIPE)
                  termsig != SIGPIPE &&
 #endif
                  signal_is_trapped (termsig) == 0)
-               {
+               {
                  fprintf (stderr, "%s: line %d: ", get_name_for_error (), line_number);
                  pretty_print_job (job, JLIST_NONINTERACTIVE, stderr);
                }
@@ -2659,16 +2859,16 @@ initialize_job_control (force)
             turn off job control.  */
          if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
            {
-             if (give_terminal_to (shell_pgrp) < 0)            /* XXX */
+             if (give_terminal_to (shell_pgrp, 0) < 0)
                {
-                 setpgid (0, original_pgrp);                   /* XXX */
-                 shell_pgrp = original_pgrp;                   /* XXX */
-                 job_control = 0;                              /* XXX */
+                 setpgid (0, original_pgrp);
+                 shell_pgrp = original_pgrp;
+                 job_control = 0;
                }
            }
        }
       if (job_control == 0)
-        internal_error ("no job control in this shell");       /* XXX */
+       internal_error ("no job control in this shell");
     }
 
   if (shell_tty != fileno (stderr))
@@ -2684,6 +2884,17 @@ initialize_job_control (force)
   return job_control;
 }
 
+#ifdef DEBUG
+void
+debug_print_pgrps ()
+{
+  itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld",
+        (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp);
+  itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld",
+        shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0));
+}
+#endif
+
 /* Set the line discipline to the best this system has to offer.
    Return -1 if this is not possible. */
 static int
@@ -2743,7 +2954,6 @@ set_new_line_discipline (tty)
 
 static SigHandler *old_tstp, *old_ttou, *old_ttin;
 static SigHandler *old_cont = (SigHandler *)SIG_DFL;
-static sighandler stop_signal_handler (), cont_signal_handler ();
 
 #if defined (TIOCGWINSZ) && defined (SIGWINCH)
 static SigHandler *old_winch = (SigHandler *)SIG_DFL;
@@ -2760,9 +2970,9 @@ get_new_window_size (from_sig)
 #if defined (aixpc)
       shell_tty_info.c_winsize = win;  /* structure copying */
 #endif
-      set_lines_and_columns (win.ws_row, win.ws_col);
+      sh_set_lines_and_columns (win.ws_row, win.ws_col);
 #if defined (READLINE)
-      _rl_set_screen_size (win.ws_row, win.ws_col);
+      rl_set_screen_size (win.ws_row, win.ws_col);
 #endif
     }
 }
@@ -2815,9 +3025,9 @@ initialize_job_signals ()
     }
   else if (job_control)
     {
-      old_tstp = set_signal_handler (SIGTSTP, stop_signal_handler);
-      old_ttou = set_signal_handler (SIGTTOU, stop_signal_handler);
-      old_ttin = set_signal_handler (SIGTTIN, stop_signal_handler);
+      old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler);
+      old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
+      old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler);
     }
   /* Leave these things alone for non-interactive shells without job
      control. */
@@ -2825,7 +3035,7 @@ initialize_job_signals ()
 
 /* Here we handle CONT signals. */
 static sighandler
-cont_signal_handler (sig)
+sigcont_sighandler (sig)
      int sig;
 {
   initialize_job_signals ();
@@ -2837,16 +3047,16 @@ cont_signal_handler (sig)
 
 /* Here we handle stop signals while we are running not as a login shell. */
 static sighandler
-stop_signal_handler (sig)
+sigstop_sighandler (sig)
      int sig;
 {
   set_signal_handler (SIGTSTP, old_tstp);
   set_signal_handler (SIGTTOU, old_ttou);
   set_signal_handler (SIGTTIN, old_ttin);
 
-  old_cont = set_signal_handler (SIGCONT, cont_signal_handler);
+  old_cont = set_signal_handler (SIGCONT, sigcont_sighandler);
 
-  give_terminal_to (shell_pgrp);
+  give_terminal_to (shell_pgrp, 0);
 
   kill (getpid (), sig);
 
@@ -2855,14 +3065,15 @@ stop_signal_handler (sig)
 
 /* Give the terminal to PGRP.  */
 int
-give_terminal_to (pgrp)
+give_terminal_to (pgrp, force)
      pid_t pgrp;
+     int force;
 {
   sigset_t set, oset;
   int r;
 
   r = 0;
-  if (job_control)
+  if (job_control || force)
     {
       sigemptyset (&set);
       sigaddset (&set, SIGTTOU);
@@ -2876,8 +3087,8 @@ give_terminal_to (pgrp)
        {
          /* Maybe we should print an error message? */
 #if 0
-         sys_error ("tcsetpgrp(%d) failed: pid %d to pgrp %d",
-           shell_tty, getpid(), pgrp);
+         sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld",
+           shell_tty, (long)getpid(), (long)pgrp);
 #endif
          r = -1;
        }
@@ -2942,10 +3153,22 @@ nohup_all_jobs (running_only)
   UNBLOCK_CHILD (oset);
 }
 
-/* Mark all dead jobs as notified, so delete_job () cleans them out
-   of the job table properly. */
+int
+count_all_jobs ()
+{
+  int i, n;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+  for (i = n = 0; i < job_slots; i++)
+    if (jobs[i] && DEADJOB(i) == 0)
+      n++;
+  UNBLOCK_CHILD (oset);
+  return n;
+}
+
 static void
-mark_dead_jobs_as_notified ()
+mark_all_jobs_as_dead ()
 {
   register int i;
   sigset_t set, oset;
@@ -2955,12 +3178,56 @@ mark_dead_jobs_as_notified ()
       BLOCK_CHILD (set, oset);
 
       for (i = 0; i < job_slots; i++)
-#if 0
-       if (jobs[i] && DEADJOB (i))
-#else
-       if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i) != last_asynchronous_pid)))
-#endif
-         jobs[i]->flags |= J_NOTIFIED;
+       if (jobs[i])
+         jobs[i]->state = JDEAD;
+
+      UNBLOCK_CHILD (oset);
+    }
+}
+
+/* Mark all dead jobs as notified, so delete_job () cleans them out
+   of the job table properly.  POSIX.2 says we need to save the
+   status of the last CHILD_MAX jobs, so we count the number of dead
+   jobs and mark only enough as notified to save CHILD_MAX statuses. */
+static void
+mark_dead_jobs_as_notified (force)
+     int force;
+{
+  register int i, ndead;
+  sigset_t set, oset;
+
+  if (job_slots)
+    {
+      BLOCK_CHILD (set, oset);
+
+      /* Count the number of dead jobs */
+      for (i = ndead = 0; force == 0 && i < job_slots; i++)
+       {
+         if (jobs[i] && DEADJOB (i))
+           ndead++;
+       }
+
+      /* Don't do anything if the number of jobs is less than CHILD_MAX and
+        we're not forcing a cleanup. */
+      if (force == 0 && ndead <= CHILD_MAX)
+       {
+         UNBLOCK_CHILD (oset);
+         return;
+       }
+
+      /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in
+        the list.  This isn't exactly right yet; changes need to be made
+        to stop_pipeline so we don't mark the newer jobs after we've
+        created CHILD_MAX slots in the jobs array. */
+      for (i = 0; i < job_slots; i++)
+       {
+         if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i) != last_asynchronous_pid)))
+           {
+             jobs[i]->flags |= J_NOTIFIED;
+             if (force == 0 && --ndead <= CHILD_MAX)
+               break;
+           }
+       }
 
       UNBLOCK_CHILD (oset);
     }
@@ -3009,7 +3276,7 @@ end_job_control ()
       terminate_stopped_jobs ();
 
       if (original_pgrp >= 0)
-       give_terminal_to (original_pgrp);
+       give_terminal_to (original_pgrp, 1);
     }
 
   if (original_pgrp >= 0)
@@ -3051,7 +3318,7 @@ pipe_read (pp)
   if (pp[0] >= 0)
     {
       while (read (pp[0], &ch, 1) == -1 && errno == EINTR)
-       continue;
+       ;
     }
 }
 
@@ -3077,33 +3344,3 @@ close_pgrp_pipe ()
 }
 
 #endif /* PGRP_PIPE */
-
-static void
-setjstatus (j)
-     int j;
-{
-#if defined (ARRAY_VARS)
-  register int i;
-  register PROCESS *p;
-
-  for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++)
-    ;
-  i++;
-  if (statsize <= i)
-    {
-      pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int));
-      statsize = i;
-    }
-  i = 0;
-  p = jobs[j]->pipe;
-  do
-    {
-      pstatuses[i++] = process_exit_status (p->status);
-      p = p->next;
-    }
-  while (p != jobs[j]->pipe);
-
-  pstatuses[i] = -1;   /* sentinel */
-  set_pipestatus_array (pstatuses);
-#endif
-}