-/* The thing that makes children, remembers them, and contains wait loops. */
+/* jobs.c - functions that make children, remember them, and handle their termination. */
/* This file works with both POSIX and BSD systems. It implements job
control. */
-/* Copyright (C) 1989-2008 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2013 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
- Bash is free software; you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free
- Software Foundation; either version 2, or (at your option) any later
- version.
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- Bash is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with Bash; see the file COPYING. If not, write to the Free Software
- Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
#include "config.h"
#include "filecntl.h"
#include <sys/ioctl.h>
+#if defined (HAVE_SYS_PARAM_H)
#include <sys/param.h>
+#endif
#if defined (BUFFERED_INPUT)
# include "input.h"
#include "bashintl.h"
#include "shell.h"
#include "jobs.h"
+#include "execute_cmd.h"
#include "flags.h"
#include "builtins/builtext.h"
extern int errno;
#endif /* !errno */
-#define DEFAULT_CHILD_MAX 32
+#if !defined (HAVE_KILLPG)
+extern int killpg __P((pid_t, int));
+#endif
+
+#if !DEFAULT_CHILD_MAX
+# define DEFAULT_CHILD_MAX 32
+#endif
+
+#if !MAX_CHILD_MAX
+# define MAX_CHILD_MAX 8192
+#endif
+
#if !defined (DEBUG)
#define MAX_JOBS_IN_ARRAY 4096 /* production */
#else
extern int posixly_correct, shell_level;
extern int last_command_exit_value, last_command_exit_signal;
extern int loop_level, breaking;
+extern int executing_list;
extern int sourcelevel;
extern int running_trap;
extern sh_builtin_func_t *this_shell_builtin;
#endif
/* Last child made by the shell. */
-pid_t last_made_pid = NO_PID;
+volatile pid_t last_made_pid = NO_PID;
/* Pid of the last asynchronous child. */
-pid_t last_asynchronous_pid = NO_PID;
+volatile pid_t last_asynchronous_pid = NO_PID;
/* The pipeline currently being built. */
PROCESS *the_pipeline = (PROCESS *)NULL;
/* If this is non-zero, $LINES and $COLUMNS are reset after every process
exits from get_tty_state(). */
-int check_window_size;
+int check_window_size = CHECKWINSIZE_DEFAULT;
/* Functions local to this file. */
static int print_job __P((JOB *, int, int, int));
static int process_exit_status __P((WAIT));
static int process_exit_signal __P((WAIT));
-static int job_exit_status __P((int));
-static int job_exit_signal __P((int));
static int set_job_status_and_cleanup __P((int));
static WAIT job_signal_status __P((int));
static void reset_current __P((void));
static void set_job_running __P((int));
static void setjstatus __P((int));
+static int maybe_give_terminal_to __P((pid_t, pid_t, int));
static void mark_all_jobs_as_dead __P((void));
static void mark_dead_jobs_as_notified __P((int));
static void restore_sigint_handler __P((void));
static char retcode_name_buffer[64];
-/* flags to detect pid wraparound */
-static pid_t first_pid = NO_PID;
-static int pid_wrap = -1;
-
#if !defined (_POSIX_VERSION)
/* These are definitions to map POSIX 1003.1 functions onto existing BSD
init_job_stats ()
{
js = zerojs;
- first_pid = NO_PID;
- pid_wrap = -1;
}
/* Return the working directory for the current process. Unlike
old_pipeline = the_pipeline;
the_pipeline = saved_pipeline;
already_making_children = saved_already_making_children;
- if (discard)
+ if (discard && old_pipeline)
discard_pipeline (old_pipeline);
}
*
*/
if (job_control && newjob->pgrp && (subshell_environment&SUBSHELL_ASYNC) == 0)
- give_terminal_to (newjob->pgrp, 0);
+ maybe_give_terminal_to (shell_pgrp, newjob->pgrp, 0);
}
}
stop_making_children ();
UNBLOCK_CHILD (oset);
- return (js.j_current);
+ return (newjob ? i : js.j_current);
}
/* Functions to manage the list of exited background pids whose status has
for (prev = p = bgpids.list; p; prev = p, p = p->next)
if (p->pid == pid)
{
- prev->next = p->next; /* remove from list */
- break;
+ prev->next = p->next; /* remove from list */
+ break;
}
if (p == 0)
bgpids.npid--;
}
}
-
+
/* Reset the values of js.j_lastj and js.j_firstj after one or both have
been deleted. The caller should check whether js.j_njobs is 0 before
calling this. This wraps around, but the rest of the code does not. At
if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
delete_job (i, 0);
}
+
+#if defined (COPROCESS_SUPPORT)
+ coproc_reap ();
+#endif
+
UNQUEUE_SIGCHLD(os);
}
static int
processes_in_job (job)
+ int job;
{
int nproc;
register PROCESS *p;
delete_job (job, DEL_NOBGPID);
else
{
- internal_warning (_("forked pid %d appears in running job %d"), pid, job);
+#ifdef DEBUG
+ internal_warning (_("forked pid %d appears in running job %d"), pid, job+1);
+#endif
if (p)
p->pid = 0;
}
}
}
-#if defined (DEBUG)
+#if 0
itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize);
itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0);
itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j);
if (js.j_current == NO_JOB || js.j_previous == NO_JOB || js.j_current > js.j_lastj || js.j_previous > js.j_lastj)
reset_current ();
-#ifdef DEBUG
+#if 0
itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous);
#endif
UNBLOCK_CHILD (oset);
}
-/* Compact the jobs list by removing dead jobs. Assumed that we have filled
+/* Compact the jobs list by removing dead jobs. Assume that we have filled
the jobs array to some predefined maximum. Called when the shell is not
the foreground process (subshell_environment != 0). Returns the first
available slot in the compacted list. If that value is js.j_jobslots, then
reap_dead_jobs ();
realloc_jobs_list ();
-#ifdef DEBUG
+#if 0
itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
#endif
}
}
+/* Create a (dummy) PROCESS with NAME, PID, and STATUS, and make it the last
+ process in jobs[JID]->pipe. Used by the lastpipe code. */
+void
+append_process (name, pid, status, jid)
+ char *name;
+ pid_t pid;
+ int status;
+ int jid;
+{
+ PROCESS *t, *p;
+
+ t = (PROCESS *)xmalloc (sizeof (PROCESS));
+ t->next = (PROCESS *)NULL;
+ t->pid = pid;
+ /* set process exit status using offset discovered by configure */
+ t->status = (status & 0xff) << WEXITSTATUS_OFFSET;
+ t->running = PS_DONE;
+ t->command = name;
+
+ js.c_reaped++; /* XXX */
+
+ for (p = jobs[jid]->pipe; p->next != jobs[jid]->pipe; p = p->next)
+ ;
+ p->next = t;
+ t->next = jobs[jid]->pipe;
+}
+
#if 0
/* Take the last job and make it the first job. Must be called with
SIGCHLD blocked. */
sigset_t set, oset;
pid_t pid;
+ /* XXX - block SIGTERM here and unblock in child after fork resets the
+ set of pending signals? */
sigemptyset (&set);
sigaddset (&set, SIGCHLD);
sigaddset (&set, SIGINT);
making_children ();
+ forksleep = 1;
+
#if defined (BUFFERED_INPUT)
/* If default_buffered_input is active, we are reading a script. If
the command is asynchronous, we have already duplicated /dev/null
sync_buffered_stream (default_buffered_input);
#endif /* BUFFERED_INPUT */
+ RESET_SIGTERM;
+
/* Create the child, handle severe errors. Retry on EAGAIN. */
while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)
{
+ /* bash-4.2 */
+ /* If we can't create any children, try to reap some dead ones. */
+ waitchld (-1, 0);
+
sys_error ("fork: retry");
+ RESET_SIGTERM;
+
if (sleep (forksleep) != 0)
break;
forksleep <<= 1;
}
+ if (pid != 0)
+ RESET_SIGTERM;
+
if (pid < 0)
{
sys_error ("fork");
if (the_pipeline)
kill_current_pipeline ();
+ last_command_exit_value = EX_NOEXEC;
throw_to_top_level (); /* Reset signals, etc. */
}
/* In the parent. Remember the pid of the child just created
as the proper pgrp if this is the first child. */
- if (first_pid == NO_PID)
- first_pid = pid;
- else if (pid_wrap == -1 && pid < first_pid)
- pid_wrap = 0;
- else if (pid_wrap == 0 && pid >= first_pid)
- pid_wrap = 1;
-
if (job_control)
{
if (pipeline_pgrp == 0)
last_asynchronous_pid = 1;
#endif
- if (pid_wrap > 0)
- delete_old_job (pid);
+ /* Delete the saved status for any job containing this PID in case it's
+ been reused. */
+ delete_old_job (pid);
-#if !defined (RECYCLES_PIDS)
- /* Only check for saved status if we've saved more than CHILD_MAX
- statuses, unless the system recycles pids. */
- if ((js.c_reaped + bgpids.npid) >= js.c_childmax)
-#endif
- bgp_delete (pid); /* new process, discard any saved status */
+ /* Perform the check for pid reuse unconditionally. Some systems reuse
+ PIDs before giving a process CHILD_MAX/_SC_CHILD_MAX unique ones. */
+ bgp_delete (pid); /* new process, discard any saved status */
last_made_pid = pid;
return r;
}
-/* Wait for all of the backgrounds of this shell to finish. */
+/* Wait for all of the background processes started by this shell to finish. */
void
wait_for_background_pids ()
{
#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids
static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;
+static int wait_sigint_received;
+static int child_caught_sigint;
+static int waiting_for_child;
+
static void
restore_sigint_handler ()
{
{
set_signal_handler (SIGINT, old_sigint_handler);
old_sigint_handler = INVALID_SIGNAL_HANDLER;
+ waiting_for_child = 0;
}
}
-static int wait_sigint_received;
-
/* Handle SIGINT while we are waiting for children in a script to exit.
The `wait' builtin should be interruptible, but all others should be
effectively ignored (i.e. not cause the shell to exit). */
if (interrupt_immediately ||
(this_shell_builtin && this_shell_builtin == wait_builtin))
{
- last_command_exit_value = EXECUTION_FAILURE;
+ last_command_exit_value = 128+SIGINT;
restore_sigint_handler ();
/* If we got a SIGINT while in `wait', and SIGINT is trapped, do
what POSIX.2 says (see builtins/wait.def for more info). */
signal_is_trapped (SIGINT) &&
((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler))
{
- interrupt_immediately = 0;
trap_handler (SIGINT); /* set pending_traps[SIGINT] */
wait_signal_received = SIGINT;
- longjmp (wait_intr_buf, 1);
+ if (interrupt_immediately)
+ {
+ interrupt_immediately = 0;
+ longjmp (wait_intr_buf, 1);
+ }
+ else
+ /* Let CHECK_WAIT_INTR handle it in wait_for/waitchld */
+ SIGRETURN (0);
}
-
- ADDINTERRUPT;
- QUIT;
+ else if (interrupt_immediately)
+ {
+ ADDINTERRUPT;
+ QUIT;
+ }
+ else /* wait_builtin but signal not trapped, treat as interrupt */
+ kill (getpid (), SIGINT);
}
/* XXX - should this be interrupt_state? If it is, the shell will act
as if it got the SIGINT interrupt. */
- wait_sigint_received = 1;
+ if (waiting_for_child)
+ wait_sigint_received = 1;
+ else
+ {
+ last_command_exit_value = 128+SIGINT;
+ restore_sigint_handler ();
+ kill (getpid (), SIGINT);
+ }
/* Otherwise effectively ignore the SIGINT and allow the running job to
be killed. */
/* Return the exit status of job JOB. This is the exit status of the last
(rightmost) process in the job's pipeline, modified if the job was killed
by a signal or stopped. */
-static int
+int
job_exit_status (job)
int job;
{
return (process_exit_status (raw_job_exit_status (job)));
}
-static int
+int
job_exit_signal (job)
int job;
{
WAIT s;
register PROCESS *child;
sigset_t set, oset;
- register PROCESS *p;
/* In the case that this code is interrupted, and we longjmp () out of it,
we are relying on the code in throw_to_top_level () to restore the
top-level signal mask. */
+ child = 0;
BLOCK_CHILD (set, oset);
/* Ignore interrupts while waiting for a job run without job control
substitution. */
/* This is possibly a race condition -- should it go in stop_pipeline? */
- wait_sigint_received = 0;
+ wait_sigint_received = child_caught_sigint = 0;
if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB))
{
old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
+ waiting_for_child = 0;
if (old_sigint_handler == SIG_IGN)
set_signal_handler (SIGINT, old_sigint_handler);
}
if (interactive && job_control == 0)
QUIT;
+ /* Check for terminating signals and exit the shell if we receive one */
+ CHECK_TERMSIG;
+
+ /* Check for a trapped signal interrupting the wait builtin and jump out */
+ CHECK_WAIT_INTR;
/* If we say wait_for (), then we have a record of this child somewhere.
If it and none of its peers are running, don't call waitchld(). */
job = NO_JOB;
do
{
- FIND_CHILD (pid, child);
+ if (pid != ANY_PID)
+ FIND_CHILD (pid, child);
/* If this child is part of a job, then we are really waiting for the
job to finish. Otherwise, we are waiting for the child to finish.
has already exited before this is called, sigchld_handler will have
called waitchld and the state will be set to JDEAD. */
- if (PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
+ if (pid == ANY_PID || PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
{
#if defined (WAITPID_BROKEN) /* SCOv4 */
sigset_t suspend_set;
sigemptyset (&act.sa_mask);
sigemptyset (&oact.sa_mask);
act.sa_flags = 0;
- sigaction (SIGCHLD, &act, &oact);
+# if defined (SA_RESTART)
+ act.sa_flags |= SA_RESTART;
# endif
+ sigaction (SIGCHLD, &act, &oact);
+# endif /* MUST_UNBLOCK_CHLD */
queue_sigchld = 1;
- r = waitchld (pid, 1);
+ waiting_for_child++;
+ r = waitchld (pid, 1); /* XXX */
+ waiting_for_child--;
+#if 0
+itrace("wait_for: blocking wait for %d returns %d child = %p", (int)pid, r, child);
+#endif
# if defined (MUST_UNBLOCK_CHLD)
sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
{
termination_state = -1;
+ /* XXX - restore sigint handler here? */
goto wait_for_return;
}
if it exists, as JDEAD. */
if (r == -1 && errno == ECHILD)
{
- child->running = PS_DONE;
- WSTATUS (child->status) = 0; /* XXX -- can't find true status */
+ if (child)
+ {
+ child->running = PS_DONE;
+ WSTATUS (child->status) = 0; /* XXX -- can't find true status */
+ }
js.c_living = 0; /* no living child processes */
if (job != NO_JOB)
{
js.c_reaped++;
js.j_ndead++;
}
+ if (pid == ANY_PID)
+ {
+ termination_state = -1;
+ break;
+ }
}
#endif /* WAITPID_BROKEN */
}
old SIGINT signal handler. */
if (interactive && job_control == 0)
QUIT;
+ /* Check for terminating signals and exit the shell if we receive one */
+ CHECK_TERMSIG;
+
+ /* Check for a trapped signal interrupting the wait builtin and jump out */
+ CHECK_WAIT_INTR;
+
+ if (pid == ANY_PID)
+ /* XXX - could set child but we don't have a handle on what waitchld
+ reaps. Leave termination_state alone. */
+ goto wait_for_return;
}
while (PRUNNING (child) || (job != NO_JOB && RUNNING (job)));
+ /* Restore the original SIGINT signal handler before we return. */
+ restore_sigint_handler ();
+
/* The exit state of the command is either the termination state of the
child, or the termination state of the job. If a job, the status
of the last child in the pipeline is the significant one. If the command
or until loop, act as if the shell received SIGINT as
well, so the loop can be broken. This doesn't call the
SIGINT signal handler; maybe it should. */
- if (signal_is_trapped (SIGINT) == 0 && loop_level)
+ if (signal_is_trapped (SIGINT) == 0 && (loop_level || (shell_compatibility_level > 32 && executing_list)))
ADDINTERRUPT;
else
{
}
}
}
- else if ((subshell_environment & SUBSHELL_COMSUB) && wait_sigint_received)
+ else if ((subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PIPE)) && wait_sigint_received)
{
/* If waiting for a job in a subshell started to do command
- substitution, simulate getting and being killed by the SIGINT to
- pass the status back to our parent. */
+ substitution or to run a pipeline element that consists of
+ something like a while loop or a for loop, simulate getting
+ and being killed by the SIGINT to pass the status back to our
+ parent. */
s = job_signal_status (job);
-
- if (WIFSIGNALED (s) && WTERMSIG (s) == SIGINT && signal_is_trapped (SIGINT) == 0)
+
+ if (child_caught_sigint == 0 && signal_is_trapped (SIGINT) == 0)
{
UNBLOCK_CHILD (oset);
- restore_sigint_handler ();
old_sigint_handler = set_signal_handler (SIGINT, SIG_DFL);
if (old_sigint_handler == SIG_IGN)
restore_sigint_handler ();
kill (getpid (), SIGINT);
}
}
+ else if (interactive_shell == 0 && IS_FOREGROUND (job) && check_window_size)
+ get_new_window_size (0, (int *)0, (int *)0);
/* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
signal handler path */
UNBLOCK_CHILD (oset);
- /* Restore the original SIGINT signal handler before we return. */
- restore_sigint_handler ();
-
return (termination_state);
}
return r;
}
+/* Wait for any background job started by this shell to finish. Very
+ similar to wait_for_background_pids(). Returns the exit status of
+ the next exiting job, -1 if there are no background jobs. The caller
+ is responsible for translating -1 into the right return value. */
+int
+wait_for_any_job ()
+{
+ pid_t pid;
+ int i, r, waited_for;
+ sigset_t set, oset;
+
+ if (jobs_list_frozen)
+ return -1;
+
+ /* First see if there are any unnotified dead jobs that we can report on */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) == 0)
+ {
+return_job:
+ r = job_exit_status (i);
+ notify_of_job_status (); /* XXX */
+ delete_job (i, 0);
+#if defined (COPROCESS_SUPPORT)
+ coproc_reap ();
+#endif
+ UNBLOCK_CHILD (oset);
+ return r;
+ }
+ }
+ UNBLOCK_CHILD (oset);
+
+ /* At this point, we have no dead jobs in the jobs table. Wait until we
+ get one, even if it takes multiple pids exiting. */
+ for (waited_for = 0;;)
+ {
+ /* Make sure there is a background job to wait for */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+ break;
+ if (i == js.j_jobslots)
+ {
+ UNBLOCK_CHILD (oset);
+ return -1;
+ }
+
+ UNBLOCK_CHILD (oset);
+
+ QUIT;
+ CHECK_TERMSIG;
+ CHECK_WAIT_INTR;
+
+ errno = 0;
+ r = wait_for (ANY_PID); /* special sentinel value for wait_for */
+ if (r == -1 && errno == ECHILD)
+ mark_all_jobs_as_dead ();
+
+ /* Now we see if we have any dead jobs and return the first one */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i] && DEADJOB (i))
+ goto return_job;
+ UNBLOCK_CHILD (oset);
+ }
+
+ return -1;
+}
+
/* Print info about dead jobs, and then delete them from the list
of known jobs. This does not actually delete jobs when the
shell is not interactive, because the dead jobs are not marked
WAIT status;
PROCESS *child;
pid_t pid;
+
int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
static int wcontinued = WCONTINUED; /* run-time fix for glibc problem */
: 0;
if (sigchld || block == 0)
waitpid_flags |= WNOHANG;
+
+ /* Check for terminating signals and exit the shell if we receive one */
CHECK_TERMSIG;
+ /* Check for a trapped signal interrupting the wait builtin and jump out */
+ CHECK_WAIT_INTR;
+
+ if (block == 1 && queue_sigchld == 0 && (waitpid_flags & WNOHANG) == 0)
+ {
+ internal_warning (_("waitchld: turning on WNOHANG to avoid indefinite block"));
+ waitpid_flags |= WNOHANG;
+ }
pid = WAITPID (-1, &status, waitpid_flags);
+#if 0
+if (wpid != -1 && block)
+ itrace("waitchld: blocking waitpid returns %d", pid);
+#endif
/* WCONTINUED may be rejected by waitpid as invalid even when defined */
if (wcontinued && pid < 0 && errno == EINVAL)
{
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)
break;
}
+#if 0
+itrace("waitchld: waitpid returns %d block = %d", pid, block);
+#endif
/* If waitpid returns 0, there are running children. If it returns -1,
the only other error POSIX says it can return is EINTR. */
CHECK_TERMSIG;
+ CHECK_WAIT_INTR;
+
+ /* If waitpid returns -1/EINTR and the shell saw a SIGINT, then we
+ assume the child has blocked or handled SIGINT. In that case, we
+ require the child to actually die due to SIGINT to act on the
+ SIGINT we received; otherwise we assume the child handled it and
+ let it go. */
+ if (pid < 0 && errno == EINTR && wait_sigint_received)
+ child_caught_sigint = 1;
+
if (pid <= 0)
continue; /* jumps right to the test */
+ /* If the child process did die due to SIGINT, forget our assumption
+ that it caught or otherwise handled it. */
+ if (WIFSIGNALED (status) && WTERMSIG (status) == SIGINT)
+ child_caught_sigint = 0;
+
/* children_exited is used to run traps on SIGCHLD. We don't want to
run the trap if a process is just being continued. */
if (WIFCONTINUED(status) == 0)
/* Locate our PROCESS for this pid. */
child = find_process (pid, 1, &job); /* want living procs only */
+#if defined (COPROCESS_SUPPORT)
+ coproc_pidchk (pid, WSTATUS(status));
+#endif
+
/* It is not an error to have a child terminate that we did
not have a record of. This child could have been part of
a pipeline in backquote substitution. Even so, I'm not
if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
trap_list[SIGCHLD] != (char *)IGNORE_SIG)
{
- if (this_shell_builtin && this_shell_builtin == wait_builtin)
+ if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin)
{
interrupt_immediately = 0;
trap_handler (SIGCHLD); /* set pending_traps[SIGCHLD] */
wait_signal_received = SIGCHLD;
- longjmp (wait_intr_buf, 1);
+ /* If we're in a signal handler, let CHECK_WAIT_INTR pick it up;
+ run_pending_traps will call run_sigchld_trap later */
+ if (sigchld == 0)
+ longjmp (wait_intr_buf, 1);
}
-
- run_sigchld_trap (children_exited);
+ /* If not in posix mode and not executing the wait builtin, queue the
+ signal for later handling. Run the trap immediately if we are
+ executing the wait builtin, but don't break out of `wait'. */
+ else if (sigchld) /* called from signal handler */
+ queue_sigchld_trap (children_exited);
+ else if (running_trap)
+ queue_sigchld_trap (children_exited);
+ else if (this_shell_builtin == wait_builtin)
+ run_sigchld_trap (children_exited); /* XXX */
+ else
+ queue_sigchld_trap (children_exited);
}
/* We have successfully recorded the useful information about this process
#endif
{
any_stopped = 1;
- any_tstped |= interactive && job_control &&
- (WSTOPSIG (child->status) == SIGTSTP);
+ any_tstped |= job_control && (WSTOPSIG (child->status) == SIGTSTP);
}
child = child->next;
}
does not exit due to SIGINT, run the trap handler but do not
otherwise act as if we got the interrupt. */
if (wait_sigint_received && interactive_shell == 0 &&
- WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
+ child_caught_sigint && IS_FOREGROUND (job) &&
signal_is_trapped (SIGINT))
{
int old_frozen;
signals are sent to process groups) or via kill(2) to the foreground
process by another process (or itself). If the shell did receive the
SIGINT, it needs to perform normal SIGINT processing. */
- else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
+ else if (wait_sigint_received &&
+ child_caught_sigint == 0 &&
IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
{
int old_frozen;
temp_handler = trap_to_sighandler (SIGINT);
restore_sigint_handler ();
if (temp_handler == SIG_DFL)
- termsig_handler (SIGINT);
+ termsig_handler (SIGINT); /* XXX */
else if (temp_handler != SIG_IGN)
(*temp_handler) (SIGINT);
}
subst_assign_varlist = (WORD_LIST *)NULL;
the_pipeline = (PROCESS *)NULL;
+ running_trap = SIGCHLD + 1;
+
set_impossible_sigchld_trap ();
jobs_list_frozen = 1;
for (i = 0; i < nchild; i++)
{
+#if 0
interrupt_immediately = 1;
+#endif
parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE);
}
run_unwind_frame ("SIGCHLD trap");
+ running_trap = 0;
}
/* Function to call when you want to notify people of changes
case JDEAD:
if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
termsig != SIGINT &&
+#if defined (DONT_REPORT_SIGTERM)
+ termsig != SIGTERM &&
+#endif
#if defined (DONT_REPORT_SIGPIPE)
termsig != SIGPIPE &&
#endif
exit (1);
}
- /* We can only have job control if we are interactive. */
- if (interactive == 0)
+ /* We can only have job control if we are interactive unless we force it. */
+ if (interactive == 0 && force == 0)
{
job_control = 0;
original_pgrp = NO_PID;
if (shell_tty == -1)
shell_tty = dup (fileno (stderr)); /* fd 2 */
- shell_tty = move_to_high_fd (shell_tty, 1, -1);
+ if (shell_tty != -1)
+ shell_tty = move_to_high_fd (shell_tty, 1, -1);
/* Compensate for a bug in systems that compiled the BSD
rlogind with DEBUG defined, like NeXT and Alliant. */
t_errno = errno;
setpgid (0, original_pgrp);
shell_pgrp = original_pgrp;
+ errno = t_errno;
+ sys_error (_("cannot set terminal process group (%d)"), shell_pgrp);
job_control = 0;
}
}
if (r == -1)
errno = e;
+
return r;
}
+/* Give terminal to NPGRP iff it's currently owned by OPGRP. FLAGS are the
+ flags to pass to give_terminal_to(). */
+static int
+maybe_give_terminal_to (opgrp, npgrp, flags)
+ pid_t opgrp, npgrp;
+ int flags;
+{
+ int tpgrp;
+
+ tpgrp = tcgetpgrp (shell_tty);
+ if (tpgrp < 0 && errno == ENOTTY)
+ return -1;
+ if (tpgrp == npgrp)
+ {
+ terminal_pgrp = npgrp;
+ return 0;
+ }
+ else if (tpgrp != opgrp)
+ {
+#if defined (DEBUG)
+ internal_warning ("maybe_give_terminal_to: terminal pgrp == %d shell pgrp = %d new pgrp = %d", tpgrp, opgrp, npgrp);
+#endif
+ return -1;
+ }
+ else
+ return (give_terminal_to (npgrp, flags));
+}
+
/* Clear out any jobs in the job array. This is intended to be used by
children of the shell, who should not have any job structures as baggage
when they start executing (forking subshells for parenthesized execution
}
#ifdef DEBUG
+# if 0
if (ndeadproc != js.c_reaped)
itrace("mark_dead_jobs_as_notified: ndeadproc (%d) != js.c_reaped (%d)", ndeadproc, js.c_reaped);
+# endif
if (ndead != js.j_ndead)
itrace("mark_dead_jobs_as_notified: ndead (%d) != js.j_ndead (%d)", ndead, js.j_ndead);
#endif
}
/* Here to allow other parts of the shell (like the trap stuff) to
- unfreeze the jobs list. */
+ freeze and unfreeze the jobs list. */
+void
+freeze_jobs_list ()
+{
+ jobs_list_frozen = 1;
+}
+
void
unfreeze_jobs_list ()
{
void
end_job_control ()
{
- if (interactive_shell) /* XXX - should it be interactive? */
+ if (interactive_shell || job_control) /* XXX - should it be just job_control? */
{
terminate_stopped_jobs ();
initialize_job_control (0);
}
+void
+set_maxchild (nchild)
+ int nchild;
+{
+ static int lmaxchild = -1;
+
+ if (lmaxchild < 0)
+ lmaxchild = getmaxchild ();
+ if (lmaxchild < 0)
+ lmaxchild = DEFAULT_CHILD_MAX;
+
+ /* Clamp value we set. Minimum is what Posix requires, maximum is defined
+ above as MAX_CHILD_MAX. */
+ if (nchild < lmaxchild)
+ nchild = lmaxchild;
+ else if (nchild > MAX_CHILD_MAX)
+ nchild = MAX_CHILD_MAX;
+
+ js.c_childmax = nchild;
+}
+
/* Set the handler to run when the shell receives a SIGCHLD signal. */
void
set_sigchld_handler ()
sh_closepipe (pgrp_pipe);
}
+void
+save_pgrp_pipe (p, clear)
+ int *p;
+ int clear;
+{
+ p[0] = pgrp_pipe[0];
+ p[1] = pgrp_pipe[1];
+ if (clear)
+ pgrp_pipe[0] = pgrp_pipe[1] = -1;
+}
+
+void
+restore_pgrp_pipe (p)
+ int *p;
+{
+ pgrp_pipe[0] = p[0];
+ pgrp_pipe[1] = p[1];
+}
+
#endif /* PGRP_PIPE */