/* This file works with both POSIX and BSD systems. It implements job
control. */
-/* Copyright (C) 1989-2017 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
#endif /* !errno */
#if !defined (HAVE_KILLPG)
-extern int killpg __P((pid_t, int));
+extern int killpg PARAMS((pid_t, int));
#endif
#if !DEFAULT_CHILD_MAX
-# define DEFAULT_CHILD_MAX 32
+# define DEFAULT_CHILD_MAX 4096
#endif
#if !MAX_CHILD_MAX
/* 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));
+typedef int sh_job_map_func_t PARAMS((JOB *, int, int, int));
/* Variables used here but defined in other files. */
-extern sigset_t top_level_mask;
extern WORD_LIST *subst_assign_varlist;
extern SigHandler **original_signals;
-extern void set_original_signal __P((int, SigHandler *));
+extern void set_original_signal PARAMS((int, SigHandler *));
static struct jobstats zerojs = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 };
struct jobstats js = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 };
ps_index_t pidstat_table[PIDSTAT_TABLE_SZ];
-struct bgpids bgpids = { 0, 0, 0 };
+struct bgpids bgpids = { 0, 0, 0, 0 };
+
+struct procchain procsubs = { 0, 0, 0 };
/* The array of known jobs. */
JOB **jobs = (JOB **)NULL;
void debug_print_pgrps (void);
-static sighandler wait_sigint_handler __P((int));
-static sighandler sigchld_handler __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, int, int *));
-static PROCESS *find_process __P((pid_t, int, int *));
-
-static char *current_working_directory __P((void));
-static char *job_working_directory __P((void));
-static char *j_strsignal __P((int));
-static char *printable_job_status __P((int, PROCESS *, int));
-
-static PROCESS *find_last_proc __P((int, 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, int, PROCESS **));
-static int print_job __P((JOB *, int, int, int));
-static int process_exit_status __P((WAIT));
-static int process_exit_signal __P((WAIT));
-static int set_job_status_and_cleanup __P((int));
-
-static WAIT job_signal_status __P((int));
-static WAIT raw_job_exit_status __P((int));
-
-static void notify_of_job_status __P((void));
-static void reset_job_indices __P((void));
-static void cleanup_dead_jobs __P((void));
-static int processes_in_job __P((int));
-static void realloc_jobs_list __P((void));
-static int compact_jobs_list __P((int));
-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 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 sighandler wait_sigint_handler PARAMS((int));
+static sighandler sigchld_handler PARAMS((int));
+static sighandler sigcont_sighandler PARAMS((int));
+static sighandler sigstop_sighandler PARAMS((int));
+
+static int waitchld PARAMS((pid_t, int));
+
+static PROCESS *find_pid_in_pipeline PARAMS((pid_t, PROCESS *, int));
+static PROCESS *find_pipeline PARAMS((pid_t, int, int *));
+static PROCESS *find_process PARAMS((pid_t, int, int *));
+
+static char *current_working_directory PARAMS((void));
+static char *job_working_directory PARAMS((void));
+static char *j_strsignal PARAMS((int));
+static char *printable_job_status PARAMS((int, PROCESS *, int));
+
+static PROCESS *find_last_proc PARAMS((int, int));
+static pid_t find_last_pid PARAMS((int, int));
+
+static int set_new_line_discipline PARAMS((int));
+static int map_over_jobs PARAMS((sh_job_map_func_t *, int, int));
+static int job_last_stopped PARAMS((int));
+static int job_last_running PARAMS((int));
+static int most_recent_job_in_state PARAMS((int, JOB_STATE));
+static int find_job PARAMS((pid_t, int, PROCESS **));
+static int print_job PARAMS((JOB *, int, int, int));
+static int process_exit_status PARAMS((WAIT));
+static int process_exit_signal PARAMS((WAIT));
+static int set_job_status_and_cleanup PARAMS((int));
+
+static WAIT job_signal_status PARAMS((int));
+static WAIT raw_job_exit_status PARAMS((int));
+
+static void notify_of_job_status PARAMS((void));
+static void reset_job_indices PARAMS((void));
+static void cleanup_dead_jobs PARAMS((void));
+static int processes_in_job PARAMS((int));
+static void realloc_jobs_list PARAMS((void));
+static int compact_jobs_list PARAMS((int));
+static void add_process PARAMS((char *, pid_t));
+static void print_pipeline PARAMS((PROCESS *, int, int, FILE *));
+static void pretty_print_job PARAMS((int, int, FILE *));
+static void set_current_job PARAMS((int));
+static void reset_current PARAMS((void));
+static void set_job_running PARAMS((int));
+static void setjstatus PARAMS((int));
+static int maybe_give_terminal_to PARAMS((pid_t, pid_t, int));
+static void mark_all_jobs_as_dead PARAMS((void));
+static void mark_dead_jobs_as_notified PARAMS((int));
+static void restore_sigint_handler PARAMS((void));
#if defined (PGRP_PIPE)
-static void pipe_read __P((int *));
+static void pipe_read PARAMS((int *));
#endif
/* Hash table manipulation */
-static ps_index_t *pshash_getbucket __P((pid_t));
-static void pshash_delindex __P((ps_index_t));
+static ps_index_t *pshash_getbucket PARAMS((pid_t));
+static void pshash_delindex PARAMS((ps_index_t));
/* Saved background process status management */
-static struct pidstat *bgp_add __P((pid_t, int));
-static int bgp_delete __P((pid_t));
-static void bgp_clear __P((void));
-static int bgp_search __P((pid_t));
+static struct pidstat *bgp_add PARAMS((pid_t, int));
+static int bgp_delete PARAMS((pid_t));
+static void bgp_clear PARAMS((void));
+static int bgp_search PARAMS((pid_t));
-static struct pipeline_saver *alloc_pipeline_saver __P((void));
+static struct pipeline_saver *alloc_pipeline_saver PARAMS((void));
-static ps_index_t bgp_getindex __P((void));
-static void bgp_resize __P((void)); /* XXX */
+static ps_index_t bgp_getindex PARAMS((void));
+static void bgp_resize PARAMS((void)); /* XXX */
#if defined (ARRAY_VARS)
static int *pstatuses; /* list of pipeline statuses */
#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++
+/* We set queue_sigchld around the call to waitchld to protect data structures
+ from a SIGCHLD arriving while waitchld is executing. */
#define UNQUEUE_SIGCHLD(os) \
do { \
queue_sigchld--; \
if (queue_sigchld == 0 && os != sigchld) \
- waitchld (-1, 0); \
+ { \
+ queue_sigchld = 1; \
+ waitchld (-1, 0); \
+ queue_sigchld = 0; \
+ } \
} while (0)
static SigHandler *old_tstp, *old_ttou, *old_ttin;
discard_pipeline (disposer);
}
+/* Not used right now */
void
discard_last_procsub_child ()
{
if (the_pipeline)
{
cleanup_the_pipeline ();
- pipeline_pgrp = 0;
+ /* If job_control == 0, pipeline_pgrp will always be equal to shell_pgrp;
+ if job_control != 0, pipeline_pgrp == shell_pgrp for command and
+ process substitution, in which case we want it to be the same as
+ shell_pgrp for the lifetime of this shell instance. */
+ if (pipeline_pgrp != shell_pgrp)
+ pipeline_pgrp = 0;
#if defined (PGRP_PIPE)
sh_closepipe (pgrp_pipe);
#endif
the_pipeline = (PROCESS *)NULL;
newjob->pgrp = pipeline_pgrp;
- pipeline_pgrp = 0;
+ if (pipeline_pgrp != shell_pgrp)
+ pipeline_pgrp = 0;
newjob->flags = 0;
if (pipefail_opt)
break;
if (orig_psi == bgpids.storage[psi].bucket_next) /* catch reported bug */
{
- internal_warning ("bgp_delete: LOOP: psi (%d) == storage[psi].bucket_next", psi);
+ internal_warning (_("bgp_delete: LOOP: psi (%d) == storage[psi].bucket_next"), psi);
return 0;
}
}
return (bgpids.storage[psi].status);
if (orig_psi == bgpids.storage[psi].bucket_next) /* catch reported bug */
{
- internal_warning ("bgp_search: LOOP: psi (%d) == storage[psi].bucket_next", psi);
+ internal_warning (_("bgp_search: LOOP: psi (%d) == storage[psi].bucket_next"), psi);
return -1;
}
}
}
#endif
+/* External interface to bgp_add; takes care of blocking and unblocking
+ SIGCHLD. Not really used. */
+void
+save_proc_status (pid, status)
+ pid_t pid;
+ int status;
+{
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+ bgp_add (pid, status);
+ UNBLOCK_CHILD (oset);
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+/* Functions to add and remove PROCESS * children from the list of running
+ asynchronous process substitutions. The list is currently a simple singly
+ linked list of PROCESS *, so it works with the set of callers that want
+ a child. subst.c:process_substitute adds to the list, the various wait*
+ functions manipulate child->running and child->status, and processes are
+ eventually removed from the list and added to the bgpids table. */
+
+static void
+procsub_free (p)
+ PROCESS *p;
+{
+ FREE (p->command);
+ free (p);
+}
+
+PROCESS *
+procsub_add (p)
+ PROCESS *p;
+{
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+ if (procsubs.head == 0)
+ {
+ procsubs.head = procsubs.end = p;
+ procsubs.nproc = 0;
+ }
+ else
+ {
+ procsubs.end->next = p;
+ procsubs.end = p;
+ }
+ procsubs.nproc++;
+ UNBLOCK_CHILD (oset);
+
+ return p;
+}
+
+PROCESS *
+procsub_search (pid)
+ pid_t pid;
+{
+ PROCESS *p;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+ for (p = procsubs.head; p; p = p->next)
+ if (p->pid == pid)
+ break;
+ UNBLOCK_CHILD (oset);
+
+ return p;
+}
+
+PROCESS *
+procsub_delete (pid)
+ pid_t pid;
+{
+ PROCESS *p, *prev;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+ for (p = prev = procsubs.head; p; prev = p, p = p->next)
+ if (p->pid == pid)
+ {
+ prev->next = p->next;
+ break;
+ }
+
+ if (p == 0)
+ {
+ UNBLOCK_CHILD (oset);
+ return p;
+ }
+
+ if (p == procsubs.head)
+ procsubs.head = procsubs.head->next;
+ else if (p == procsubs.end)
+ procsubs.end = prev;
+
+ procsubs.nproc--;
+ if (procsubs.nproc == 0)
+ procsubs.head = procsubs.end = 0;
+ else if (procsubs.nproc == 1) /* XXX */
+ procsubs.end = procsubs.head;
+
+ /* this can't be called anywhere in a signal handling path */
+ bgp_add (p->pid, process_exit_status (p->status));
+ UNBLOCK_CHILD (oset);
+ return (p);
+}
+
+int
+procsub_waitpid (pid)
+ pid_t pid;
+{
+ PROCESS *p;
+ int r;
+
+ p = procsub_search (pid);
+ if (p == 0)
+ return -1;
+ if (p->running == PS_DONE)
+ return (p->status);
+ r = wait_for (p->pid, 0);
+ return (r); /* defer removing until later */
+}
+
+void
+procsub_waitall ()
+{
+ PROCESS *p;
+ int r;
+
+ for (p = procsubs.head; p; p = p->next)
+ {
+ if (p->running == PS_DONE)
+ continue;
+ r = wait_for (p->pid, 0);
+ }
+}
+
+void
+procsub_clear ()
+{
+ PROCESS *p, *ps;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+ for (ps = procsubs.head; ps; )
+ {
+ p = ps;
+ ps = ps->next;
+ procsub_free (p);
+ }
+ procsubs.head = procsubs.end = 0;
+ procsubs.nproc = 0;
+ UNBLOCK_CHILD (oset);
+}
+
+/* Must be called with SIGCHLD blocked. */
+void
+procsub_prune ()
+{
+ PROCESS *ohead, *oend, *ps, *p;
+ int onproc;
+
+ if (procsubs.nproc == 0)
+ return;
+
+ ohead = procsubs.head;
+ oend = procsubs.end;
+ onproc = procsubs.nproc;
+
+ procsubs.head = procsubs.end = 0;
+ procsubs.nproc = 0;
+
+ for (p = ohead; p; )
+ {
+ ps = p->next;
+ p->next = 0;
+ if (p->running == PS_DONE)
+ {
+ bgp_add (p->pid, process_exit_status (p->status));
+ procsub_free (p);
+ }
+ else
+ procsub_add (p);
+ p = ps;
+ }
+}
+#endif
+
/* 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
js.j_firstj++;
}
if (js.j_firstj == old)
- js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
}
if (jobs[js.j_lastj] == 0)
{
js.j_lastj--;
}
if (js.j_lastj == old)
- js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
}
}
}
#if defined (PROCESS_SUBSTITUTION)
- if (last_procsub_child && last_procsub_child->running == PS_DONE)
- {
- bgp_add (last_procsub_child->pid, process_exit_status (last_procsub_child->status)); /* XXX */
- discard = last_procsub_child;
- last_procsub_child = (PROCESS *)NULL;
- discard_pipeline (discard);
- }
+ procsub_prune ();
+ last_procsub_child = (PROCESS *)NULL;
#endif
#if defined (COPROCESS_SUPPORT)
js.c_injobs -= ndel;
if (temp->state == JDEAD)
{
- js.c_reaped -= ndel;
+ /* XXX - save_pipeline and restore_pipeline (e.g., for DEBUG trap) can
+ mess with this total. */
+ js.c_reaped -= ndel; /* assumes proc hadn't been reaped earlier */
js.j_ndead--;
if (js.c_reaped < 0)
{
{
# ifdef DEBUG
if (j == NO_JOB)
- internal_warning (_("add_process: process %5ld (%s) in the_pipeline"), (long)p->pid, p->command);
+ internal_warning ("add_process: process %5ld (%s) in the_pipeline", (long)p->pid, p->command);
# endif
if (PALIVE (p))
- internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command);
+ internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command);
p->running = PS_RECYCLED; /* mark as recycled */
}
#endif
start_pipeline ();
}
+static PROCESS *
+find_pid_in_pipeline (pid, pipeline, alive_only)
+ pid_t pid;
+ PROCESS *pipeline;
+ int alive_only;
+{
+ PROCESS *p;
+
+ p = pipeline;
+ do
+ {
+ /* Return it if we found it. Don't ever return a recycled pid. */
+ if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
+ return (p);
+
+ p = p->next;
+ }
+ while (p != pipeline);
+ return ((PROCESS *)NULL);
+}
+
/* 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.
If JOBP is non-null, return the index of the job containing PID. */
{
int job;
PROCESS *p;
+ struct pipeline_saver *save;
/* See if this process is in the pipeline that we are building. */
+ p = (PROCESS *)NULL;
if (jobp)
*jobp = NO_JOB;
- if (the_pipeline)
- {
- p = the_pipeline;
- do
- {
- /* Return it if we found it. Don't ever return a recycled pid. */
- if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
- return (p);
- p = p->next;
- }
- while (p != the_pipeline);
- }
- /* Now look in the last process substitution pipeline, since that sets $! */
- if (last_procsub_child)
- {
- p = last_procsub_child;
- do
- {
- /* Return it if we found it. Don't ever return a recycled pid. */
- if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
- return (p);
+ if (the_pipeline && (p = find_pid_in_pipeline (pid, the_pipeline, alive_only)))
+ return (p);
- p = p->next;
- }
- while (p != last_procsub_child);
- }
+ /* Is this process in a saved pipeline? */
+ for (save = saved_pipeline; save; save = save->next)
+ if (save->pipeline && (p = find_pid_in_pipeline (pid, save->pipeline, alive_only)))
+ return (p);
+
+#if defined (PROCESS_SUBSTITUTION)
+ if (procsubs.nproc > 0 && (p = procsub_search (pid)) && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
+ return (p);
+#endif
job = find_job (pid, alive_only, &p);
if (jobp)
/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as
required by find_job. */
int
-get_job_by_pid (pid, block)
+get_job_by_pid (pid, block, procp)
pid_t pid;
int block;
+ PROCESS **procp;
{
int job;
sigset_t set, oset;
if (block)
BLOCK_CHILD (set, oset);
- job = find_job (pid, 0, NULL);
+ job = find_job (pid, 0, procp);
if (block)
UNBLOCK_CHILD (oset);
reported asynchronously, so just add the CR if the shell is
currently interactive and asynchronous notification is enabled. */
if (asynchronous_notification && interactive)
- fprintf (stream, "\r\n");
- else
- fprintf (stream, "\n");
+ putc ('\r', stream);
+ fprintf (stream, "\n");
}
if (p == last)
int format, ignore, job_index;
{
pretty_print_job (job_index, format, stdout);
+ cleanup_dead_jobs ();
}
void
anything else with it. ASYNC_P says what to do with the tty. If
non-zero, then don't give it away. */
pid_t
-make_child (command, async_p)
+make_child (command, flags)
char *command;
- int async_p;
+ int flags;
{
- int forksleep;
- sigset_t set, oset;
+ int async_p, forksleep;
+ sigset_t set, oset, termset, chldset, oset_copy;
pid_t pid;
+ SigHandler *oterm;
+
+ sigemptyset (&oset_copy);
+ sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &oset_copy);
+ sigaddset (&oset_copy, SIGTERM);
- /* XXX - block SIGTERM here and unblock in child after fork resets the
- set of pending signals? */
+ /* Block SIGTERM here and unblock in child after fork resets the
+ set of pending signals. */
sigemptyset (&set);
sigaddset (&set, SIGCHLD);
sigaddset (&set, SIGINT);
+ sigaddset (&set, SIGTERM);
+
sigemptyset (&oset);
sigprocmask (SIG_BLOCK, &set, &oset);
+ /* Blocked in the parent, child will receive it after unblocking SIGTERM */
+ if (interactive_shell)
+ oterm = set_signal_handler (SIGTERM, SIG_DFL);
+
making_children ();
+ async_p = (flags & FORK_ASYNC);
forksleep = 1;
#if defined (BUFFERED_INPUT)
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 */
- sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+ /* keep SIGTERM blocked until we reset the handler to SIG_IGN */
+ sigprocmask (SIG_SETMASK, &oset_copy, (sigset_t *)NULL);
/* If we can't create any children, try to reap some dead ones. */
waitchld (-1, 0);
errno = EAGAIN; /* restore errno */
sys_error ("fork: retry");
- RESET_SIGTERM;
if (sleep (forksleep) != 0)
break;
}
if (pid != 0)
- RESET_SIGTERM;
+ if (interactive_shell)
+ set_signal_handler (SIGTERM, oterm);
if (pid < 0)
{
if (the_pipeline)
kill_current_pipeline ();
- last_command_exit_value = EX_NOEXEC;
+ set_exit_status (EX_NOEXEC);
throw_to_top_level (); /* Reset signals, etc. */
}
CLRINTERRUPT; /* XXX - children have their own interrupt state */
- /* Restore top-level signal mask. */
- sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
-
+ /* Restore top-level signal mask, including unblocking SIGTERM */
+ restore_sigmask ();
+
if (job_control)
{
/* All processes in this pipeline belong in the same
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 && ((subshell_environment&(SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0) && running_in_background == 0)
+ if ((flags & FORK_NOTERM) == 0 && async_p == 0 && pipeline_pgrp != shell_pgrp && ((subshell_environment&(SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0) && running_in_background == 0)
give_terminal_to (pipeline_pgrp, 0);
#if defined (PGRP_PIPE)
sh_closepipe (pgrp_pipe);
#endif /* PGRP_PIPE */
-#if 0
/* Don't set last_asynchronous_pid in the child */
- if (async_p)
- last_asynchronous_pid = mypid; /* XXX */
- else
-#endif
+
#if defined (RECYCLES_PIDS)
if (last_asynchronous_pid == mypid)
- /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
+ /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
last_asynchronous_pid = 1;
#endif
}
last_asynchronous_pid = pid;
#if defined (RECYCLES_PIDS)
else if (last_asynchronous_pid == pid)
- /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
+ /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
last_asynchronous_pid = 1;
#endif
delete_old_job (pid);
/* Perform the check for pid reuse unconditionally. Some systems reuse
- PIDs before giving a process CHILD_MAX/_SC_CHILD_MAX unique ones. */
+ 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;
js.c_totforked++;
js.c_living++;
- /* Unblock SIGINT and SIGCHLD unless creating a pipeline, in which case
- SIGCHLD remains blocked until all commands in the pipeline have been
- created. */
+ /* Unblock SIGTERM, SIGINT, and SIGCHLD unless creating a pipeline, in
+ which case SIGCHLD remains blocked until all commands in the pipeline
+ have been created (execute_cmd.c:execute_pipeline()). */
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
}
alive = 0;
do
{
- r = wait_for (pid);
+ r = wait_for (pid, 0);
if ((flags & JWAIT_FORCE) == 0)
break;
bgp_delete (pid);
}
+ /* Check for a trapped signal interrupting the wait builtin and jump out */
+ CHECK_WAIT_INTR;
+
return r;
}
/* Wait for all of the background processes started by this shell to finish. */
void
-wait_for_background_pids ()
+wait_for_background_pids (ps)
+ struct procstat *ps;
{
register int i, r;
int any_stopped, check_async;
QUIT;
errno = 0; /* XXX */
r = wait_for_single_pid (pid, JWAIT_PERROR);
+ if (ps)
+ {
+ ps->pid = pid;
+ ps->status = (r < 0) ? 127 : r;
+ }
if (r == -1 && errno == ECHILD)
{
/* If we're mistaken about job state, compensate. */
}
#if defined (PROCESS_SUBSTITUTION)
- if (last_procsub_child && last_procsub_child->pid != NO_PID)
- r = wait_for (last_procsub_child->pid);
- wait_procsubs ();
- reap_procsubs ();
-#if 0
- /* We don't want to wait indefinitely if we have stopped children. */
- if (any_stopped == 0)
- {
- /* Check whether or not we have any unreaped children. */
- while ((r = wait_for (ANY_PID)) >= 0)
- {
- QUIT;
- CHECK_WAIT_INTR;
- }
- }
-#endif
+ procsub_waitall ();
#endif
/* POSIX.2 says the shell can discard the statuses of all completed jobs if
static int wait_sigint_received;
static int child_caught_sigint;
-static int waiting_for_child;
+
+int waiting_for_child;
/* Clean up state after longjmp to wait_intr_buf */
void
{
SigHandler *sigint_handler;
- if (interrupt_immediately ||
- (this_shell_builtin && this_shell_builtin == wait_builtin))
+ if (this_shell_builtin && this_shell_builtin == wait_builtin)
{
- last_command_exit_value = 128+SIGINT;
+ set_exit_status (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). */
{
trap_handler (SIGINT); /* set pending_traps[SIGINT] */
wait_signal_received = SIGINT;
- if (interrupt_immediately && wait_intr_flag)
- {
- interrupt_immediately = 0;
- sh_longjmp (wait_intr_buf, 1);
- }
+ if (wait_intr_flag)
+ sh_longjmp (wait_intr_buf, 1);
else
/* Let CHECK_WAIT_INTR handle it in wait_for/waitchld */
SIGRETURN (0);
}
- else if (interrupt_immediately)
- {
- ADDINTERRUPT;
- QUIT;
- }
else /* wait_builtin but signal not trapped, treat as interrupt */
kill (getpid (), SIGINT);
}
wait_sigint_received = 1;
else
{
- last_command_exit_value = 128+SIGINT;
+ set_exit_status (128+SIGINT);
restore_sigint_handler ();
kill (getpid (), SIGINT);
}
int fail;
WAIT ret;
-#if 0
- if (pipefail_opt)
-#else
if (jobs[job]->flags & J_PIPEFAIL)
-#endif
{
fail = 0;
p = jobs[job]->pipe;
the jobs table. Returns -1 if waitchld() returns -1, indicating
that there are no unwaited-for child processes. */
int
-wait_for (pid)
+wait_for (pid, flags)
pid_t pid;
+ int flags;
{
int job, termination_state, r;
WAIT s;
temp_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
if (temp_sigint_handler == wait_sigint_handler)
- {
+ {
#if defined (DEBUG)
internal_warning ("wait_for: recursively setting old_sigint_handler to wait_sigint_handler: running_trap = %d", running_trap);
#endif
- }
+ }
else
old_sigint_handler = temp_sigint_handler;
waiting_for_child = 0;
if (pid == ANY_PID || PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
{
-#if defined (WAITPID_BROKEN) /* SCOv4 */
- sigset_t suspend_set;
- sigemptyset (&suspend_set);
- sigsuspend (&suspend_set);
-#else /* !WAITPID_BROKEN */
-# if defined (MUST_UNBLOCK_CHLD)
- struct sigaction act, oact;
- sigset_t nullset, chldset;
+ int old_waiting;
queue_sigchld = 1;
- sigemptyset (&nullset);
- sigemptyset (&chldset);
- sigprocmask (SIG_SETMASK, &nullset, &chldset);
- act.sa_handler = SIG_DFL;
- sigemptyset (&act.sa_mask);
- sigemptyset (&oact.sa_mask);
- act.sa_flags = 0;
-# if defined (SA_RESTART)
- act.sa_flags |= SA_RESTART;
-# endif
- sigaction (SIGCHLD, &act, &oact);
-# endif /* MUST_UNBLOCK_CHLD */
- queue_sigchld = 1;
- waiting_for_child++;
+ old_waiting = waiting_for_child;
+ waiting_for_child = 1;
+ /* XXX - probably not strictly necessary but we want to catch
+ everything that happened before we switch the behavior of
+ trap_handler to longjmp on a trapped signal (waiting_for_child) */
+ CHECK_WAIT_INTR;
r = waitchld (pid, 1); /* XXX */
- waiting_for_child--;
+ waiting_for_child = old_waiting;
#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);
-# endif
queue_sigchld = 0;
if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
{
break;
}
}
-#endif /* WAITPID_BROKEN */
}
/* If the shell is interactive, and job control is disabled, see
conditions to determine whether or not it should undo this and
give the terminal to pipeline_pgrp. */
- if (running_in_background == 0 && (subshell_environment&(SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0)
+ if ((flags & JWAIT_NOTERM) == 0 && running_in_background == 0 &&
+ (subshell_environment & (SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0)
give_terminal_to (shell_pgrp, 0);
}
}
/* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
- signal handler path */
+ signal handler path */
if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/)
setjstatus (job);
includes JWAIT_FORCE, we wait for the job to terminate, no just change
state */
int
-wait_for_job (job, flags)
+wait_for_job (job, flags, ps)
int job, flags;
+ struct procstat *ps;
{
pid_t pid;
int r, state;
do
{
- r = wait_for (pid);
+ r = wait_for (pid, 0);
if (r == -1 && errno == ECHILD)
mark_all_jobs_as_dead ();
+ CHECK_WAIT_INTR;
+
if ((flags & JWAIT_FORCE) == 0)
break;
jobs[job]->flags |= J_NOTIFIED;
UNBLOCK_CHILD (oset);
+ if (ps)
+ {
+ ps->pid = pid;
+ ps->status = (r < 0) ? 127 : r;
+ }
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. */
+ is responsible for translating -1 into the right return value. RPID,
+ if non-null, gets the pid of the job's process leader. */
int
-wait_for_any_job (flags)
+wait_for_any_job (flags, ps)
int flags;
+ struct procstat *ps;
{
pid_t pid;
int i, r;
BLOCK_CHILD (set, oset);
for (i = 0; i < js.j_jobslots; i++)
{
+ if ((flags & JWAIT_WAITING) && jobs[i] && IS_WAITING (i) == 0)
+ continue; /* if we don't want it, skip it */
if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) == 0)
{
return_job:
r = job_exit_status (i);
+ pid = find_last_pid (i, 0);
+ if (ps)
+ {
+ ps->pid = pid;
+ ps->status = r;
+ }
notify_of_job_status (); /* XXX */
delete_job (i, 0);
#if defined (COPROCESS_SUPPORT)
/* 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 (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+ break;
if (i == js.j_jobslots)
{
UNBLOCK_CHILD (oset);
CHECK_WAIT_INTR;
errno = 0;
- r = wait_for (ANY_PID); /* special sentinel value for wait_for */
+ r = wait_for (ANY_PID, 0); /* 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;
+ {
+ if ((flags & JWAIT_WAITING) && jobs[i] && IS_WAITING (i) == 0)
+ continue; /* if we don't want it, skip it */
+ if (jobs[i] && DEADJOB (i))
+ goto return_job;
+ }
UNBLOCK_CHILD (oset);
}
BLOCK_CHILD (set, oset);
+ if ((subshell_environment & SUBSHELL_COMSUB) && (pipeline_pgrp == shell_pgrp))
+ {
+ internal_error (_("%s: no current jobs"), this_command_name);
+ UNBLOCK_CHILD (oset);
+ return (-1);
+ }
+
if (DEADJOB (job))
{
internal_error (_("%s: job has terminated"), this_command_name);
pid = find_last_pid (job, 0);
UNBLOCK_CHILD (oset);
- st = wait_for (pid);
+ st = wait_for (pid, 0);
shell_tty_info = save_stty;
set_tty_state ();
return (st);
/* 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;
+ 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. */
+ run the trap if a process is just being continued. */
if (WIFCONTINUED(status) == 0)
{
children_exited++;
#if defined (PROCESS_SUBSTITUTION)
/* Only manipulate the list of process substitutions while SIGCHLD
- is blocked. */
+ is blocked. We only use this as a hint that we can remove FIFOs
+ or close file descriptors corresponding to terminated process
+ substitutions. */
if ((ind = find_procsub_child (pid)) >= 0)
set_procsub_status (ind, pid, WSTATUS (status));
- /* XXX - save in bgpids list? */
#endif
/* It is not an error to have a child terminate that we did
{
if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin)
{
- interrupt_immediately = 0;
/* This was trap_handler (SIGCHLD) but that can lose traps if
children_exited > 1 */
queue_sigchld_trap (children_exited);
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)
+ if (asynchronous_notification && interactive && executing_builtin == 0)
notify_of_job_status ();
return (children_exited);
/* 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. */
+ 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);
unwind_protect_int (last_command_exit_value);
unwind_protect_int (last_command_exit_signal);
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);
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);
}
substitution, so don't print anything.
Otherwise, if the shell is not interactive, POSIX says that `jobs'
is the only way to notify of job status. */
-#if 1
- if ((job_control == 0 && interactive_shell) ||
- (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB)))
-#else /* TAG:bash-5.1 */
if ((job_control == 0 && interactive_shell) ||
(startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB)) ||
(startup_state == 2 && posixly_correct && (subshell_environment & SUBSHELL_COMSUB) == 0))
-#endif
{
/* POSIX.2 compatibility: if the shell is not interactive,
hang onto the job corresponding to the last asynchronous
if (interactive)
get_tty_state ();
- if (js.c_childmax < 0)
- js.c_childmax = getmaxchild ();
- if (js.c_childmax < 0)
- js.c_childmax = DEFAULT_CHILD_MAX;
-
-#if 0
- if (js.c_childmax > MAX_CHILD_MAX)
- js.c_childmax = MAX_CHILD_MAX;
-#endif
+ set_maxchild (0);
return job_control;
}
way to avoid pid aliasing and reuse problems than keeping the POSIX-
mandated CHILD_MAX jobs around. delete_job() takes care of keeping the
bgpids list regulated. */
-
+
/* Count the number of dead jobs */
/* XXX could use js.j_firstj here */
for (i = ndead = ndeadproc = 0; i < js.j_jobslots; i++)
#endif
if (js.c_childmax < 0)
- js.c_childmax = getmaxchild ();
- if (js.c_childmax < 0)
- js.c_childmax = DEFAULT_CHILD_MAX;
-
-#if 0
- if (js.c_childmax > MAX_CHILD_MAX)
- js.c_childmax = MAX_CHILD_MAX;
-#endif
+ set_maxchild (0);
/* Don't do anything if the number of dead processes is less than CHILD_MAX
and we're not forcing a cleanup. */
if (terminal_pgrp == NO_PID)
terminal_pgrp = tcgetpgrp (shell_tty);
-
+
+ /* If we're turning on job control we're going to want to know the shell's
+ process group. */
+ if (job_control != old && job_control)
+ shell_pgrp = getpgid (0);
+
running_in_background = (terminal_pgrp != shell_pgrp);
#if 0
initialize_job_control (0);
}
+/* Set the maximum number of background children we keep track of to NCHILD.
+ If the caller passes NCHILD as 0 or -1, this ends up setting it to
+ LMAXCHILD, which is initialized the first time through. */
void
set_maxchild (nchild)
int nchild;
{
static int lmaxchild = -1;
+ /* Initialize once. */
if (lmaxchild < 0)
- lmaxchild = getmaxchild ();
+ {
+ errno = 0;
+ lmaxchild = getmaxchild ();
+ if (lmaxchild < 0 && errno == 0)
+ lmaxchild = MAX_CHILD_MAX; /* assume unlimited */
+ }
if (lmaxchild < 0)
lmaxchild = DEFAULT_CHILD_MAX;