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
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"
# 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>
#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. */
# 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;
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. */
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
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;
*
*/
if (job_control && newjob->pgrp)
- give_terminal_to (newjob->pgrp);
+ give_terminal_to (newjob->pgrp, 0);
}
}
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);
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)
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++)
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:
{
PROCESS *first, *last, *show;
int es, name_padding;
- char retcode_name_buffer[20], *temp;
+ char *temp;
if (p == 0)
return;
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)
{
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) ");
}
}
/* 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;
}
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;
{
/* 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,
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)
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);
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
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
/* 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;
}
/* 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 */
/* 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;
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;
}
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);
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. */
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;
}
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
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)))
{
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 */
}
/* 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
{
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
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
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. */
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;
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;
}
if (jobs_list_frozen)
return;
- if (interactive || interactive_shell == 0)
+ if (interactive || interactive_shell == 0 || sourcelevel)
notify_of_job_status ();
cleanup_dead_jobs ();
void
reap_dead_jobs ()
{
- mark_dead_jobs_as_notified ();
+ mark_dead_jobs_as_notified (0);
cleanup_dead_jobs ();
}
/* 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));
/* 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));
candidate = NO_JOB;
if (STOPPED (current_job))
{
- candidate = last_stopped_job (current_job);
+ candidate = job_last_stopped (current_job);
if (candidate != NO_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)
{
/* 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
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);
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;
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;
}
else
result = killpg (pid, sig);
+
+ UNBLOCK_CHILD (oset);
}
else
result = kill (pid, sig);
- UNBLOCK_CHILD (oset);
return (result);
}
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;
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;
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);
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
sigset_t set, oset;
WAIT s;
+ if (jobs == 0 || job_slots == 0)
+ return;
+
sigemptyset (&set);
sigaddset (&set, SIGCHLD);
sigaddset (&set, SIGTTOU);
{
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;
}
{
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);
}
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))
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
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;
#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
}
}
}
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. */
/* Here we handle CONT signals. */
static sighandler
-cont_signal_handler (sig)
+sigcont_sighandler (sig)
int sig;
{
initialize_job_signals ();
/* 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);
/* 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);
{
/* 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;
}
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;
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);
}
terminate_stopped_jobs ();
if (original_pgrp >= 0)
- give_terminal_to (original_pgrp);
+ give_terminal_to (original_pgrp, 1);
}
if (original_pgrp >= 0)
if (pp[0] >= 0)
{
while (read (pp[0], &ch, 1) == -1 && errno == EINTR)
- continue;
+ ;
}
}
}
#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
-}