Imported from ../bash-2.05b.tar.gz.
[platform/upstream/bash.git] / jobs.c
diff --git a/jobs.c b/jobs.c
index d50db68..2bcb3ac 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -83,9 +83,8 @@
 extern int errno;
 #endif /* !errno */
 
-#if !defined (CHILD_MAX)
-#  define CHILD_MAX 32
-#endif
+#define DEFAULT_CHILD_MAX 32
+#define MAX_JOBS_IN_ARRAY 4096         /* testing */
 
 /* 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
@@ -141,7 +140,7 @@ extern void rl_set_screen_size __P((int, int));
 #endif
 
 /* Variables used here but defined in other files. */
-extern int startup_state, subshell_environment, line_number;
+extern int subshell_environment, line_number;
 extern int posixly_correct, shell_level;
 extern int interrupt_immediately, last_command_exit_value;
 extern int loop_level, breaking;
@@ -150,13 +149,9 @@ 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 int wait_signal_received;
 extern WORD_LIST *subst_assign_varlist;
 
-#if defined (ARRAY_VARS)
-static int *pstatuses;         /* list of pipeline statuses */
-static int statsize;
-#endif
-
 /* The array of known jobs. */
 JOB **jobs = (JOB **)NULL;
 
@@ -224,21 +219,20 @@ static sighandler sigstop_sighandler __P((int));
 
 static int waitchld __P((pid_t, int));
 
-static PROCESS *find_pipeline __P((pid_t));
+static PROCESS *find_pipeline __P((pid_t, int, int *));
 
 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 pid_t find_last_pid __P((int, 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 find_job __P((pid_t, int));
 static int print_job __P((JOB *, int, int, int));
 static int process_exit_status __P((WAIT));
 static int job_exit_status __P((int));
@@ -248,6 +242,7 @@ static WAIT raw_job_exit_status __P((int));
 
 static void notify_of_job_status __P((void));
 static void cleanup_dead_jobs __P((void));
+static int compact_jobs_list __P((int));
 static void discard_pipeline __P((PROCESS *));
 static void add_process __P((char *, pid_t));
 static void print_pipeline __P((PROCESS *, int, int, FILE *));
@@ -264,9 +259,31 @@ static void pipe_read __P((int *));
 static void pipe_close __P((int *));
 #endif
 
-/* Used to synchronize between wait_for and the SIGCHLD signal handler. */
+#if defined (ARRAY_VARS)
+static int *pstatuses;         /* list of pipeline statuses */
+static int statsize;
+#endif
+
+/* Used to synchronize between wait_for and other functions and the SIGCHLD
+   signal handler. */
 static int sigchld;
-static int waiting_for_job;
+static int queue_sigchld;
+
+#define QUEUE_SIGCHLD(os)      (os) = sigchld, queue_sigchld++
+
+#define UNQUEUE_SIGCHLD(os) \
+       do { \
+         queue_sigchld--; \
+         if (queue_sigchld == 0 && os != sigchld) \
+           waitchld (-1, 0); \
+       } while (0)
+
+static SigHandler *old_tstp, *old_ttou, *old_ttin;
+static SigHandler *old_cont = (SigHandler *)SIG_DFL;
+
+#if defined (TIOCGWINSZ) && defined (SIGWINCH)
+static SigHandler *old_winch = (SigHandler *)SIG_DFL;
+#endif
 
 /* A place to temporarily save the current pipeline. */
 static PROCESS *saved_pipeline;
@@ -280,6 +297,8 @@ static int jobs_list_frozen;
 
 static char retcode_name_buffer[64];
 
+static long child_max = -1L;
+
 #if !defined (_POSIX_VERSION)
 
 /* These are definitions to map POSIX 1003.1 functions onto existing BSD
@@ -466,6 +485,12 @@ stop_pipeline (async, deferred)
     }
 
   /* Do we need more room? */
+
+  /* First try compaction */
+  if (subshell_environment && interactive_shell && i == job_slots && job_slots >= MAX_JOBS_IN_ARRAY)
+    i = compact_jobs_list (0);
+
+  /* If we can't compact, reallocate */
   if (i == job_slots)
     {
       job_slots += JOB_SLOTS;
@@ -561,18 +586,86 @@ static void
 cleanup_dead_jobs ()
 {
   register int i;
-  sigset_t set, oset;
+  int os;
 
   if (job_slots == 0 || jobs_list_frozen)
     return;
 
-  BLOCK_CHILD (set, oset);
+  QUEUE_SIGCHLD(os);
 
   for (i = 0; i < job_slots; i++)
     if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
       delete_job (i, 0);
 
+  UNQUEUE_SIGCHLD(os);
+}
+
+/* Compact the jobs list by removing dead jobs.  Assumed 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 job_slots, then
+   the list needs to be reallocated.  The jobs array is in new memory if
+   this returns > 0 and < job_slots.  FLAGS is reserved for future use. */
+static int
+compact_jobs_list (flags)
+     int flags;
+{
+  sigset_t set, oset;
+  register int i, j;
+  int nremove, ndel;
+  JOB **newlist;
+
+  if (job_slots == 0 || jobs_list_frozen)
+    return job_slots;
+
+  if (child_max < 0)
+    child_max = getmaxchild ();
+
+  /* Take out at most a quarter of the jobs in the jobs array, but leave at
+     least child_max */
+  nremove = job_slots >> 2;
+  if ((job_slots - nremove) < child_max)
+    nremove = job_slots - child_max;
+
+  /* need to increase jobs list to at least CHILD_MAX entries */
+  if (nremove < 0)
+    return job_slots;
+
+  BLOCK_CHILD (set, oset);  
+
+  for (ndel = i = 0; i < job_slots; i++)
+    if (jobs[i])
+      {
+       if (DEADJOB (i) && (find_last_pid (i, 0) != last_asynchronous_pid))
+         {
+           delete_job (i, 0);
+           ndel++;
+           if (ndel == nremove)
+             break;
+         }
+      }
+
+  if (ndel == 0)
+    {
+      UNBLOCK_CHILD (oset);
+      return job_slots;
+    }
+
+  newlist = (JOB **)xmalloc ((1 + job_slots) * sizeof (JOB *));
+  for (i = j = 0; i < job_slots; i++)
+    if (jobs[i])
+      newlist[j++] = jobs[i];
+
+  ndel = j;
+  for ( ; j < job_slots; j++)
+    newlist[j] = (JOB *)NULL;
+
+  free (jobs);
+  jobs = newlist;
+
   UNBLOCK_CHILD (oset);
+
+  return ndel;
 }
 
 /* Delete the job at INDEX from the job list.  Must be called
@@ -650,7 +743,7 @@ add_process (name, pid)
   t->next = the_pipeline;
   t->pid = pid;
   WSTATUS (t->status) = 0;
-  t->running = 1;
+  t->running = PS_RUNNING;
   t->command = name;
   the_pipeline = t;
 
@@ -792,13 +885,17 @@ kill_current_pipeline ()
 /* Return the pipeline that PID belongs to.  Note that the pipeline
    doesn't have to belong to a job.  Must be called with SIGCHLD blocked. */
 static PROCESS *
-find_pipeline (pid)
+find_pipeline (pid, running_only, jobp)
      pid_t pid;
+     int running_only;
+     int *jobp;                /* index into jobs list or NO_JOB */
 {
   int job;
   register PROCESS *p;
 
   /* See if this process is in the pipeline that we are building. */
+  if (jobp)
+    *jobp = NO_JOB;
   if (the_pipeline)
     {
       p = the_pipeline;
@@ -806,23 +903,28 @@ find_pipeline (pid)
        {
          /* Return it if we found it. */
          if (p->pid == pid)
-           return (p);
+           {
+             if ((running_only && PRUNNING(p)) || (running_only == 0))
+               return (p);
+           }
 
          p = p->next;
        }
       while (p != the_pipeline);
     }
 
-  job = find_job (pid);
-
+  job = find_job (pid, running_only);
+  if (jobp)
+    *jobp = job;
   return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe;
 }
 
 /* Return the job index that PID belongs to, or NO_JOB if it doesn't
    belong to any job.  Must be called with SIGCHLD blocked. */
 static int
-find_job (pid)
+find_job (pid, running_only)
      pid_t pid;
+     int running_only;
 {
   register int i;
   register PROCESS *p;
@@ -836,7 +938,10 @@ find_job (pid)
          do
            {
              if (p->pid == pid)
-               return (i);
+               {
+                 if ((running_only && PRUNNING(p)) || (running_only == 0))
+                   return (i);
+               }
 
              p = p->next;
            }
@@ -859,7 +964,9 @@ get_job_by_pid (pid, block)
 
   if (block)
     BLOCK_CHILD (set, oset);
-  job = find_job (pid);
+
+  job = find_job (pid, 0);
+
   if (block)
     UNBLOCK_CHILD (oset);
 
@@ -876,7 +983,7 @@ describe_pid (pid)
 
   BLOCK_CHILD (set, oset);
 
-  job = find_job (pid);
+  job = find_job (pid, 0);
 
   if (job != NO_JOB)
     printf ("[%d] %ld\n", job + 1, (long)pid);
@@ -1056,31 +1163,26 @@ print_pipeline (p, job_index, format, stream)
   fflush (stream);
 }
 
+/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT.
+   Must be called with SIGCHLD blocked or queued with queue_sigchld */
 static void
 pretty_print_job (job_index, format, stream)
      int job_index, format;
      FILE *stream;
 {
   register PROCESS *p;
-  sigset_t set, oset;
-
-  BLOCK_CHILD (set, oset);
 
   /* Format only pid information about the process group leader? */
   if (format == JLIST_PID_ONLY)
     {
       fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid);
-      UNBLOCK_CHILD (oset);
       return;
     }
 
   if (format == JLIST_CHANGED_ONLY)
     {
       if (IS_NOTIFIED (job_index))
-       {
-         UNBLOCK_CHILD (oset);
-         return;
-       }
+       return;
       format = JLIST_STANDARD;
     }
 
@@ -1099,8 +1201,6 @@ pretty_print_job (job_index, format, stream)
   /* We have printed information about this job.  When the job's
      status changes, waitchld () sets the notification flag to 0. */
   jobs[job_index]->flags |= J_NOTIFIED;
-
-  UNBLOCK_CHILD (oset);
 }
 
 static int
@@ -1118,7 +1218,7 @@ list_one_job (job, format, ignore, job_index)
      JOB *job;
      int format, ignore, job_index;
 {
-  print_job (job, format, -1, job_index);
+  pretty_print_job (job_index, format, stdout);
 }
 
 void
@@ -1317,6 +1417,7 @@ make_child (command, async_p)
   return (pid);
 }
 
+/* These two functions are called only in child processes. */
 void
 ignore_tty_job_signals ()
 {
@@ -1465,38 +1566,32 @@ set_tty_state ()
 
 /* Given an index into the jobs array JOB, return the pid of the last
    process in that job's pipeline.  This is the one whose exit status
-   counts. */
+   counts.  Must be called with SIGCHLD blocked or queued. */
 static pid_t
-find_last_pid (job)
+find_last_pid (job, block)
      int job;
+     int block;
 {
   register PROCESS *p;
+  sigset_t set, oset;
+
+  if (block)
+    BLOCK_CHILD (set, oset);
 
   p = jobs[job]->pipe;
   while (p->next != jobs[job]->pipe)
     p = p->next;
 
-  return (p->pid);
-}
-
-static pid_t
-last_pid (job)
-     int job;
-{
-  pid_t pid;
-  sigset_t set, oset;
-
-  BLOCK_CHILD (set, oset);
-  pid = find_last_pid (job);
-  UNBLOCK_CHILD (oset);
+  if (block)
+    UNBLOCK_CHILD (oset);
 
-  return (pid);
+  return (p->pid);
 }
 
 /* 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
-   (whatever wait_for returns).  If the child is not found in the
+   a child of this shell.  It returns -1 if it fails, or whatever
+   wait_for returns otherwise.  If the child is not found in the
    jobs table, it returns 127. */
 int
 wait_for_single_pid (pid)
@@ -1507,7 +1602,7 @@ wait_for_single_pid (pid)
   int r, job;
 
   BLOCK_CHILD (set, oset);
-  child = find_pipeline (pid);
+  child = find_pipeline (pid, 0, (int *)NULL);
   UNBLOCK_CHILD (oset);
 
   if (child == 0)
@@ -1521,7 +1616,7 @@ wait_for_single_pid (pid)
   /* 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);
+  job = find_job (pid, 0);
   if (job != NO_JOB && jobs[job] && DEADJOB (job))
     jobs[job]->flags |= J_NOTIFIED;
   UNBLOCK_CHILD (oset);
@@ -1533,7 +1628,7 @@ wait_for_single_pid (pid)
 void
 wait_for_background_pids ()
 {
-  register int i, count, r, waited_for;
+  register int i, r, waited_for;
   sigset_t set, oset;
   pid_t pid;
 
@@ -1541,37 +1636,31 @@ wait_for_background_pids ()
     {
       BLOCK_CHILD (set, oset);
 
-      count = 0;
+      /* find first running job; if none running in foreground, break */
       for (i = 0; i < job_slots; i++)
        if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
-         {
-           count++;
-           break;
-         }
+         break;
 
-      if (count == 0)
+      if (i == job_slots)
        {
          UNBLOCK_CHILD (oset);
          break;
        }
 
-      for (i = 0; i < job_slots; i++)
-       if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
-         {
-           pid = last_pid (i);
-           UNBLOCK_CHILD (oset);
-           QUIT;
-           errno = 0;          /* XXX */
-           r = wait_for_single_pid (pid);
-           if (r == -1)
-             {
-               if (errno == ECHILD)
-                 mark_all_jobs_as_dead ();
-             }
-           else
-             waited_for++;
-           break;
-         }
+      /* now wait for the last pid in that job. */
+      pid = find_last_pid (i, 0);
+      UNBLOCK_CHILD (oset);
+      QUIT;
+      errno = 0;               /* XXX */
+      r = wait_for_single_pid (pid);
+      if (r == -1)
+       {
+         /* If we're mistaken about job state, compensate. */
+         if (errno == ECHILD)
+           mark_all_jobs_as_dead ();
+       }
+      else
+       waited_for++;
     }
 
   /* POSIX.2 says the shell can discard the statuses of all completed jobs if
@@ -1618,6 +1707,7 @@ wait_sigint_handler (sig)
        {
          interrupt_immediately = 0;
          trap_handler (SIGINT);        /* set pending_traps[SIGINT] */
+         wait_signal_received = SIGINT;
          longjmp (wait_intr_buf, 1);
        }
       
@@ -1671,7 +1761,7 @@ job_exit_status (job)
 #define FIND_CHILD(pid, child) \
   do \
     { \
-      child = find_pipeline (pid); \
+      child = find_pipeline (pid, 0, (int *)NULL); \
       if (child == 0) \
        { \
          give_terminal_to (shell_pgrp, 0); \
@@ -1731,7 +1821,7 @@ wait_for (pid)
         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);
+       job = find_job (pid, 0);
 
       /* waitchld() takes care of setting the state of the job.  If the job
         has already exited before this is called, sigchld_handler will have
@@ -1757,13 +1847,13 @@ wait_for (pid)
          act.sa_flags = 0;
          sigaction (SIGCHLD, &act, &oact);
 #  endif
-         waiting_for_job = 1;
+         queue_sigchld = 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;
+         queue_sigchld = 0;
          if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
            {
              termination_state = -1;
@@ -1776,8 +1866,8 @@ wait_for (pid)
             if it exists, as JDEAD. */
          if (r == -1 && errno == ECHILD)
            {
-             child->running = 0;
-             child->status = 0;        /* XXX */
+             child->running = PS_DONE;
+             child->status = 0;        /* XXX -- can't find true status */
              if (job != NO_JOB)
                jobs[job]->state = JDEAD;
            }
@@ -1886,6 +1976,11 @@ if (job == NO_JOB)
            }
        }
 
+      /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
+         signal handler path */
+      if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/)
+       setjstatus (job);
+
       /* 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
@@ -1918,9 +2013,9 @@ wait_for_job (job)
   BLOCK_CHILD(set, oset);
   if (JOBSTATE (job) == JSTOPPED)
     internal_warning ("wait_for_job: job %d is stopped", job+1);
-  UNBLOCK_CHILD(oset);
 
-  pid = last_pid (job);
+  pid = find_last_pid (job, 0);
+  UNBLOCK_CHILD(oset);
   r = wait_for (pid);
 
   /* POSIX.2: we can remove the job from the jobs table if we just waited
@@ -1970,6 +2065,7 @@ most_recent_job_in_state (job, state)
   sigset_t set, oset;
 
   BLOCK_CHILD (set, oset);
+
   for (result = NO_JOB, i = job - 1; i >= 0; i--)
     {
       if (jobs[i] && (JOBSTATE (i) == state))
@@ -1978,6 +2074,7 @@ most_recent_job_in_state (job, state)
          break;
        }
     }
+
   UNBLOCK_CHILD (oset);
 
   return (result);
@@ -2110,7 +2207,7 @@ set_job_running (job)
   do
     {
       if (WIFSTOPPED (p->status))
-       p->running = 1;
+       p->running = PS_RUNNING;        /* XXX - could be PS_STOPPED */
       p = p->next;
     }
   while (p != jobs[job]->pipe);
@@ -2209,14 +2306,13 @@ start_job (job, foreground)
       killpg (jobs[job]->pgrp, SIGCONT);
     }
 
-  UNBLOCK_CHILD (oset);
-
   if (foreground)
     {
       pid_t pid;
       int s;
 
-      pid = last_pid (job);
+      pid = find_last_pid (job, 0);
+      UNBLOCK_CHILD (oset);
       s = wait_for (pid);
       shell_tty_info = save_stty;
       set_tty_state ();
@@ -2224,7 +2320,6 @@ start_job (job, foreground)
     }
   else
     {
-      BLOCK_CHILD (set, oset);
       reset_current ();
       UNBLOCK_CHILD (oset);
       return (0);
@@ -2248,8 +2343,7 @@ kill_pid (pid, sig, group)
   if (group)
     {
       BLOCK_CHILD (set, oset);
-      p = find_pipeline (pid);
-      job = find_job (pid);
+      p = find_pipeline (pid, 0, &job);
 
       if (job != NO_JOB)
        {
@@ -2263,7 +2357,7 @@ kill_pid (pid, sig, group)
              do
                {
                  kill (p->pid, sig);
-                 if (p->running == 0 && (sig == SIGTERM || sig == SIGHUP))
+                 if (p->running == PS_DONE && (sig == SIGTERM || sig == SIGHUP))
                    kill (p->pid, SIGCONT);
                  p = p->next;
                }
@@ -2307,7 +2401,7 @@ sigchld_handler (sig)
   REINSTALL_SIGCHLD_HANDLER;
   sigchld++;
   n = 0;
-  if (waiting_for_job == 0)
+  if (queue_sigchld == 0)
     n = waitchld (-1, 0);
   errno = oerrno;
   SIGRETURN (n);
@@ -2370,7 +2464,7 @@ waitchld (wpid, block)
        children_exited++;
 
       /* Locate our PROCESS for this pid. */
-      child = find_pipeline (pid);
+      child = find_pipeline (pid, 1, &job);    /* want running procs only */
 
       /* It is not an error to have a child terminate that we did
         not have a record of.  This child could have been part of
@@ -2384,10 +2478,8 @@ waitchld (wpid, block)
 
       /* Remember status, and whether or not the process is running. */
       child->status = status;
-      child->running = WIFCONTINUED(status) ? 1 : 0;
+      child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE;
 
-      job = find_job (pid);
-  
       if (job == NO_JOB)
        continue;
 
@@ -2453,7 +2545,7 @@ set_job_status_and_cleanup (job)
   do
     {
       job_state |= child->running;
-      if (child->running == 0 && (WIFSTOPPED (child->status)))
+      if (child->running == PS_DONE && (WIFSTOPPED (child->status)))
        {
          any_stopped = 1;
          any_tstped |= interactive && job_control &&
@@ -2492,8 +2584,10 @@ set_job_status_and_cleanup (job)
     {
       jobs[job]->state = JDEAD;
 
+#if 0
       if (IS_FOREGROUND (job))
        setjstatus (job);
+#endif
 
       /* If this job has a cleanup function associated with it, call it
         with `cleanarg' as the single argument, then set the function
@@ -2523,12 +2617,14 @@ set_job_status_and_cleanup (job)
          WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
          signal_is_trapped (SIGINT))
        {
+         int old_frozen;
          wait_sigint_received = 0;
          last_command_exit_value = process_exit_status (child->status);
 
+         old_frozen = jobs_list_frozen;
          jobs_list_frozen = 1;
          tstatus = maybe_call_trap_handler (SIGINT);
-         jobs_list_frozen = 0;
+         jobs_list_frozen = old_frozen;
        }
 
       /* If the foreground job is killed by SIGINT when job control is not
@@ -2543,6 +2639,8 @@ set_job_status_and_cleanup (job)
       else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
              IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
        {
+         int old_frozen;
+
          wait_sigint_received = 0;
 
          /* If SIGINT is trapped, set the exit status so that the trap
@@ -2555,9 +2653,10 @@ set_job_status_and_cleanup (job)
            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. */
+         old_frozen = jobs_list_frozen;
          jobs_list_frozen = 1;
          tstatus = maybe_call_trap_handler (SIGINT);
-         jobs_list_frozen = 0;
+         jobs_list_frozen = old_frozen;
          if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
            {
              /* wait_sigint_handler () has already seen SIGINT and
@@ -2600,7 +2699,7 @@ setjstatus (j)
   for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++)
     ;
   i++;
-  if (statsize <= i)
+  if (statsize < i)
     {
       pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int));
       statsize = i;
@@ -2615,7 +2714,7 @@ setjstatus (j)
   while (p != jobs[j]->pipe);
 
   pstatuses[i] = -1;   /* sentinel */
-  set_pipestatus_array (pstatuses);
+  set_pipestatus_array (pstatuses, i);
 #endif
 }
 
@@ -2675,11 +2774,16 @@ notify_of_job_status ()
   if (jobs == 0 || job_slots == 0)
     return;
 
-  sigemptyset (&set);
-  sigaddset (&set, SIGCHLD);
-  sigaddset (&set, SIGTTOU);
-  sigemptyset (&oset);
-  sigprocmask (SIG_BLOCK, &set, &oset);
+  if (old_ttou != 0)
+    {
+      sigemptyset (&set);
+      sigaddset (&set, SIGCHLD);
+      sigaddset (&set, SIGTTOU);
+      sigemptyset (&oset);
+      sigprocmask (SIG_BLOCK, &set, &oset);
+    }
+  else
+    queue_sigchld++;
 
   for (job = 0, dir = (char *)NULL; job < job_slots; job++)
     {
@@ -2706,7 +2810,7 @@ notify_of_job_status ()
                 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)))
+             if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid)))
                jobs[job]->flags |= J_NOTIFIED;
              continue;
            }
@@ -2776,7 +2880,10 @@ notify_of_job_status ()
            }
        }
     }
-  sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+  if (old_ttou != 0)
+    sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+  else
+    queue_sigchld--;
 }
 
 /* Initialize the job control mechanism, and set up the tty stuff. */
@@ -2821,11 +2928,11 @@ initialize_job_control (force)
        {
          if (shell_pgrp != terminal_pgrp)
            {
-             SigHandler *old_ttin;
+             SigHandler *ottin;
 
-             old_ttin = set_signal_handler(SIGTTIN, SIG_DFL);
+             ottin = set_signal_handler(SIGTTIN, SIG_DFL);
              kill (0, SIGTTIN);
-             set_signal_handler (SIGTTIN, old_ttin);
+             set_signal_handler (SIGTTIN, ottin);
              continue;
            }
          break;
@@ -2952,12 +3059,7 @@ set_new_line_discipline (tty)
 #endif
 }
 
-static SigHandler *old_tstp, *old_ttou, *old_ttin;
-static SigHandler *old_cont = (SigHandler *)SIG_DFL;
-
 #if defined (TIOCGWINSZ) && defined (SIGWINCH)
-static SigHandler *old_winch = (SigHandler *)SIG_DFL;
-
 static void
 get_new_window_size (from_sig)
      int from_sig;
@@ -3026,8 +3128,8 @@ initialize_job_signals ()
   else if (job_control)
     {
       old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler);
-      old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
       old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler);
+      old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
     }
   /* Leave these things alone for non-interactive shells without job
      control. */
@@ -3173,16 +3275,16 @@ mark_all_jobs_as_dead ()
   register int i;
   sigset_t set, oset;
 
-  if (job_slots)
-    {
-      BLOCK_CHILD (set, oset);
+  if (job_slots == 0)
+    return;
 
-      for (i = 0; i < job_slots; i++)
-       if (jobs[i])
-         jobs[i]->state = JDEAD;
+  BLOCK_CHILD (set, oset);
 
-      UNBLOCK_CHILD (oset);
-    }
+  for (i = 0; i < job_slots; i++)
+    if (jobs[i])
+      jobs[i]->state = JDEAD;
+
+  UNBLOCK_CHILD (oset);
 }
 
 /* Mark all dead jobs as notified, so delete_job () cleans them out
@@ -3196,41 +3298,62 @@ mark_dead_jobs_as_notified (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++;
-       }
+  if (job_slots == 0)
+    return;
 
-      /* 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;
-       }
+  BLOCK_CHILD (set, oset);
 
-      /* 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. */
+  /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses
+     around; just run through the array. */
+  if (force)
+    {
       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;
-           }
+         if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
+           jobs[i]->flags |= J_NOTIFIED;
        }
+      UNBLOCK_CHILD (oset);
+      return;
+    }
+
+  /* Mark enough dead jobs as notified to keep CHILD_MAX jobs left in the
+     array not marked as notified. */
+          
+  /* Count the number of dead jobs */
+  for (i = ndead = 0; i < job_slots; i++)
+    {
+      if (jobs[i] && DEADJOB (i))
+       ndead++;
+    }
+
+  if (child_max < 0)
+    child_max = getmaxchild ();
+  if (child_max < 0)
+    child_max = DEFAULT_CHILD_MAX;
 
+  /* Don't do anything if the number of dead jobs is less than CHILD_MAX and
+     we're not forcing a cleanup. */
+  if (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, 0) != last_asynchronous_pid)))
+       {
+         jobs[i]->flags |= J_NOTIFIED;
+         if (--ndead <= child_max)
+           break;
+       }
+    }
+
+  UNBLOCK_CHILD (oset);
 }
 
 /* Here to allow other parts of the shell (like the trap stuff) to