#include "config.h"
#include "bashtypes.h"
#include "trap.h"
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#if defined (HAVE_UNISTD_H)
# include <unistd.h>
#endif
#include "posixtime.h"
#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE)
# include <sys/resource.h>
#endif
#include <sys/file.h>
#include "filecntl.h"
#include <sys/ioctl.h>
#include <sys/param.h>
#if defined (BUFFERED_INPUT)
# include "input.h"
#endif
#include "shtty.h"
#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER)
# include <bsdtty.h>
#endif
#if !defined (STRUCT_WINSIZE_IN_SYS_IOCTL)
# if defined (HAVE_SYS_PTEM_H) && defined (TIOCGWINSZ) && defined (SIGWINCH)
# if defined (HAVE_SYS_STREAM_H)
# include <sys/stream.h>
# endif
# include <sys/ptem.h>
# endif
#endif
#include "bashansi.h"
#include "shell.h"
#include "jobs.h"
#include "flags.h"
#include "builtins/builtext.h"
#include "builtins/common.h"
#if !defined (errno)
extern int errno;
#endif
#define DEFAULT_CHILD_MAX 32
#define MAX_JOBS_IN_ARRAY 4096
#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION)
# define WAITPID(pid, statusp, options) \
wait3 ((union wait *)statusp, options, (struct rusage *)0)
#else
# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID)
# define WAITPID(pid, statusp, options) \
waitpid ((pid_t)pid, statusp, options)
# else
# if defined (HAVE_WAIT3)
# define WAITPID(pid, statusp, options) \
wait3 (statusp, options, (struct rusage *)0)
# else
# define WAITPID(pid, statusp, options) \
wait3 (statusp, options, (int *)0)
# endif
# endif
#endif
#if defined (GETPGRP_VOID)
# define getpgid(p) getpgrp ()
#else
# define getpgid(p) getpgrp (p)
#endif
#if defined (MUST_REINSTALL_SIGHANDLERS)
# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler)
#else
# define REINSTALL_SIGCHLD_HANDLER
#endif
#if !defined (WCONTINUED)
# define WCONTINUED 0
# define WIFCONTINUED(s) (0)
#endif
#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 __P((int, int));
#endif
extern int subshell_environment, line_number;
extern int posixly_correct, shell_level;
extern int interrupt_immediately, last_command_exit_value;
extern int loop_level, breaking;
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 int wait_signal_received;
extern WORD_LIST *subst_assign_varlist;
JOB **jobs = (JOB **)NULL;
int job_slots = 0;
int shell_tty = -1;
pid_t shell_pgrp = NO_PID;
pid_t terminal_pgrp = NO_PID;
pid_t original_pgrp = NO_PID;
pid_t pipeline_pgrp = (pid_t)0;
#if defined (PGRP_PIPE)
int pgrp_pipe[2] = { -1, -1 };
#endif
int current_job = NO_JOB;
int previous_job = NO_JOB;
pid_t last_made_pid = NO_PID;
pid_t last_asynchronous_pid = NO_PID;
PROCESS *the_pipeline = (PROCESS *)NULL;
int job_control = 1;
int already_making_children = 0;
int check_window_size;
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, int, int *));
static char *current_working_directory __P((void));
static char *job_working_directory __P((void));
static char *printable_job_status __P((int, PROCESS *, int));
static pid_t find_last_pid __P((int, 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));
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 int compact_jobs_list __P((int));
static void discard_pipeline __P((PROCESS *));
static void add_process __P((char *, pid_t));
static void print_pipeline __P((PROCESS *, int, int, FILE *));
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 __P((int *));
static void pipe_close __P((int *));
#endif
#if defined (ARRAY_VARS)
static int *pstatuses;
static int statsize;
#endif
static int sigchld;
static int queue_sigchld;
#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++
#define UNQUEUE_SIGCHLD(os) \
do { \
queue_sigchld--; \
if (queue_sigchld == 0 && os != sigchld) \
waitchld (-1, 0); \
} while (0)
static SigHandler *old_tstp, *old_ttou, *old_ttin;
static SigHandler *old_cont = (SigHandler *)SIG_DFL;
#if defined (TIOCGWINSZ) && defined (SIGWINCH)
static SigHandler *old_winch = (SigHandler *)SIG_DFL;
#endif
static PROCESS *saved_pipeline;
static int saved_already_making_children;
static int jobs_list_frozen;
static char retcode_name_buffer[64];
static long child_max = -1L;
#if !defined (_POSIX_VERSION)
#define setpgid(pid, pgrp) setpgrp (pid, pgrp)
#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp))
pid_t
tcgetpgrp (fd)
int fd;
{
pid_t pgrp;
if (ioctl (fd, TIOCGPGRP, &pgrp) < 0)
return (-1);
return (pgrp);
}
#endif
static char *
current_working_directory ()
{
char *dir;
static char d[PATH_MAX];
dir = get_string_value ("PWD");
if (dir == 0 && the_current_working_directory && no_symbolic_links)
dir = the_current_working_directory;
if (dir == 0)
{
dir = getcwd (d, sizeof(d));
if (dir)
dir = d;
}
return (dir == 0) ? "<unknown>" : dir;
}
static char *
job_working_directory ()
{
char *dir;
dir = get_string_value ("PWD");
if (dir)
return (savestring (dir));
dir = get_working_directory ("job-working-directory");
if (dir)
return (dir);
return (savestring ("<unknown>"));
}
void
making_children ()
{
if (already_making_children)
return;
already_making_children = 1;
start_pipeline ();
}
void
stop_making_children ()
{
already_making_children = 0;
}
void
cleanup_the_pipeline ()
{
if (the_pipeline)
{
discard_pipeline (the_pipeline);
the_pipeline = (PROCESS *)NULL;
}
}
void
save_pipeline (clear)
int clear;
{
saved_pipeline = the_pipeline;
saved_already_making_children = already_making_children;
if (clear)
the_pipeline = (PROCESS *)NULL;
}
void
restore_pipeline (discard)
int discard;
{
PROCESS *old_pipeline;
old_pipeline = the_pipeline;
the_pipeline = saved_pipeline;
already_making_children = saved_already_making_children;
if (discard)
discard_pipeline (old_pipeline);
}
void
start_pipeline ()
{
if (the_pipeline)
{
cleanup_the_pipeline ();
pipeline_pgrp = 0;
#if defined (PGRP_PIPE)
pipe_close (pgrp_pipe);
#endif
}
#if defined (PGRP_PIPE)
if (job_control)
{
if (pipe (pgrp_pipe) == -1)
sys_error ("start_pipeline: pgrp pipe");
}
#endif
}
int
stop_pipeline (async, deferred)
int async;
COMMAND *deferred;
{
register int i, j;
JOB *newjob;
sigset_t set, oset;
BLOCK_CHILD (set, oset);
#if defined (PGRP_PIPE)
pipe_close (pgrp_pipe);
#endif
cleanup_dead_jobs ();
if (job_slots == 0)
{
job_slots = JOB_SLOTS;
jobs = (JOB **)xmalloc (job_slots * sizeof (JOB *));
for (i = 0; i < job_slots; i++)
jobs[i] = (JOB *)NULL;
}
if (interactive)
{
for (i = job_slots; i; i--)
if (jobs[i - 1])
break;
}
else
{
for (i = 0; i < job_slots; i++)
if (jobs[i] == 0)
break;
}
if (subshell_environment && interactive_shell && i == job_slots && job_slots >= MAX_JOBS_IN_ARRAY)
i = compact_jobs_list (0);
if (i == job_slots)
{
job_slots += JOB_SLOTS;
jobs = (JOB **)xrealloc (jobs, ((1 + job_slots) * sizeof (JOB *)));
for (j = i; j < job_slots; j++)
jobs[j] = (JOB *)NULL;
}
if (the_pipeline)
{
register PROCESS *p;
int any_alive, any_stopped;
newjob = (JOB *)xmalloc (sizeof (JOB));
for (p = the_pipeline; p->next != the_pipeline; p = p->next)
;
p->next = (PROCESS *)NULL;
newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *);
for (p = newjob->pipe; p->next; p = p->next)
;
p->next = newjob->pipe;
the_pipeline = (PROCESS *)NULL;
newjob->pgrp = pipeline_pgrp;
pipeline_pgrp = 0;
newjob->flags = 0;
if (job_control)
newjob->flags |= J_JOBCONTROL;
p = newjob->pipe;
any_alive = any_stopped = 0;
do
{
any_alive |= p->running;
any_stopped |= WIFSTOPPED (p->status);
p = p->next;
}
while (p != newjob->pipe);
newjob->state = any_alive ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD);
newjob->wd = job_working_directory ();
newjob->deferred = deferred;
newjob->j_cleanup = (sh_vptrfunc_t *)NULL;
newjob->cleanarg = (PTR_T) NULL;
jobs[i] = newjob;
if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND))
setjstatus (i);
}
else
newjob = (JOB *)NULL;
if (async)
{
if (newjob)
newjob->flags &= ~J_FOREGROUND;
reset_current ();
}
else
{
if (newjob)
{
newjob->flags |= J_FOREGROUND;
if (job_control && newjob->pgrp)
give_terminal_to (newjob->pgrp, 0);
}
}
stop_making_children ();
UNBLOCK_CHILD (oset);
return (current_job);
}
static void
cleanup_dead_jobs ()
{
register int i;
int os;
if (job_slots == 0 || jobs_list_frozen)
return;
QUEUE_SIGCHLD(os);
for (i = 0; i < job_slots; i++)
if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
delete_job (i, 0);
UNQUEUE_SIGCHLD(os);
}
static int
compact_jobs_list (flags)
int flags;
{
sigset_t set, oset;
register int i, j;
int nremove, ndel;
JOB **newlist;
if (job_slots == 0 || jobs_list_frozen)
return job_slots;
if (child_max < 0)
child_max = getmaxchild ();
nremove = job_slots >> 2;
if ((job_slots - nremove) < child_max)
nremove = job_slots - child_max;
if (nremove < 0)
return job_slots;
BLOCK_CHILD (set, oset);
for (ndel = i = 0; i < job_slots; i++)
if (jobs[i])
{
if (DEADJOB (i) && (find_last_pid (i, 0) != last_asynchronous_pid))
{
delete_job (i, 0);
ndel++;
if (ndel == nremove)
break;
}
}
if (ndel == 0)
{
UNBLOCK_CHILD (oset);
return job_slots;
}
newlist = (JOB **)xmalloc ((1 + job_slots) * sizeof (JOB *));
for (i = j = 0; i < job_slots; i++)
if (jobs[i])
newlist[j++] = jobs[i];
ndel = j;
for ( ; j < job_slots; j++)
newlist[j] = (JOB *)NULL;
free (jobs);
jobs = newlist;
UNBLOCK_CHILD (oset);
return ndel;
}
void
delete_job (job_index, warn_stopped)
int job_index, warn_stopped;
{
register JOB *temp;
if (job_slots == 0 || jobs_list_frozen)
return;
if (warn_stopped && subshell_environment == 0 && STOPPED (job_index))
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)
reset_current ();
jobs[job_index] = (JOB *)NULL;
free (temp->wd);
discard_pipeline (temp->pipe);
if (temp->deferred)
dispose_command (temp->deferred);
free (temp);
}
void
nohup_job (job_index)
int job_index;
{
register JOB *temp;
if (job_slots == 0)
return;
if (temp = jobs[job_index])
temp->flags |= J_NOHUP;
}
static void
discard_pipeline (chain)
register PROCESS *chain;
{
register PROCESS *this, *next;
this = chain;
do
{
next = this->next;
FREE (this->command);
free (this);
this = next;
}
while (this != chain);
}
static void
add_process (name, pid)
char *name;
pid_t pid;
{
PROCESS *t, *p;
t = (PROCESS *)xmalloc (sizeof (PROCESS));
t->next = the_pipeline;
t->pid = pid;
WSTATUS (t->status) = 0;
t->running = PS_RUNNING;
t->command = name;
the_pipeline = t;
if (t->next == 0)
t->next = t;
else
{
p = t->next;
while (p->next != t->next)
p = p->next;
p->next = t;
}
}
#if 0
int
rotate_the_pipeline ()
{
PROCESS *p;
if (the_pipeline->next == the_pipeline)
return;
for (p = the_pipeline; p->next != the_pipeline; p = p->next)
;
the_pipeline = p;
}
int
reverse_the_pipeline ()
{
PROCESS *p, *n;
if (the_pipeline->next == the_pipeline)
return;
for (p = the_pipeline; p->next != the_pipeline; p = p->next)
;
p->next = (PROCESS *)NULL;
n = REVERSE_LIST (the_pipeline, PROCESS *);
the_pipeline = n;
for (p = the_pipeline; p->next; p = p->next)
;
p->next = the_pipeline;
}
#endif
static int
map_over_jobs (func, arg1, arg2)
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++)
{
if (jobs[i])
{
result = (*func)(jobs[i], arg1, arg2, i);
if (result)
break;
}
}
UNBLOCK_CHILD (oset);
return (result);
}
void
terminate_current_pipeline ()
{
if (pipeline_pgrp && pipeline_pgrp != shell_pgrp)
{
killpg (pipeline_pgrp, SIGTERM);
killpg (pipeline_pgrp, SIGCONT);
}
}
void
terminate_stopped_jobs ()
{
register int i;
for (i = 0; i < job_slots; i++)
{
if (jobs[i] && STOPPED (i))
{
killpg (jobs[i]->pgrp, SIGTERM);
killpg (jobs[i]->pgrp, SIGCONT);
}
}
}
void
hangup_all_jobs ()
{
register int i;
for (i = 0; i < job_slots; i++)
{
if (jobs[i])
{
if ((jobs[i]->flags & J_NOHUP) == 0)
killpg (jobs[i]->pgrp, SIGHUP);
if (STOPPED (i))
killpg (jobs[i]->pgrp, SIGCONT);
}
}
}
void
kill_current_pipeline ()
{
stop_making_children ();
start_pipeline ();
}
static PROCESS *
find_pipeline (pid, running_only, jobp)
pid_t pid;
int running_only;
int *jobp;
{
int job;
register PROCESS *p;
if (jobp)
*jobp = NO_JOB;
if (the_pipeline)
{
p = the_pipeline;
do
{
if (p->pid == pid)
{
if ((running_only && PRUNNING(p)) || (running_only == 0))
return (p);
}
p = p->next;
}
while (p != the_pipeline);
}
job = find_job (pid, running_only);
if (jobp)
*jobp = job;
return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe;
}
static int
find_job (pid, running_only)
pid_t pid;
int running_only;
{
register int i;
register PROCESS *p;
for (i = 0; i < job_slots; i++)
{
if (jobs[i])
{
p = jobs[i]->pipe;
do
{
if (p->pid == pid)
{
if ((running_only && PRUNNING(p)) || (running_only == 0))
return (i);
}
p = p->next;
}
while (p != jobs[i]->pipe);
}
}
return (NO_JOB);
}
int
get_job_by_pid (pid, block)
pid_t pid;
int block;
{
int job;
sigset_t set, oset;
if (block)
BLOCK_CHILD (set, oset);
job = find_job (pid, 0);
if (block)
UNBLOCK_CHILD (oset);
return job;
}
void
describe_pid (pid)
pid_t pid;
{
int job;
sigset_t set, oset;
BLOCK_CHILD (set, oset);
job = find_job (pid, 0);
if (job != NO_JOB)
printf ("[%d] %ld\n", job + 1, (long)pid);
else
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;
}
static void
print_pipeline (p, job_index, format, stream)
PROCESS *p;
int job_index, format;
FILE *stream;
{
PROCESS *first, *last, *show;
int es, name_padding;
char *temp;
if (p == 0)
return;
first = last = p;
while (last->next != first)
last = last->next;
for (;;)
{
if (p != first)
fprintf (stream, format ? " " : " |");
if (format != JLIST_STANDARD)
fprintf (stream, "%5ld", (long)p->pid);
fprintf (stream, " ");
if (format > -1 && job_index >= 0)
{
show = format ? p : last;
temp = printable_job_status (job_index, show, format);
if (p != first)
{
if (format)
{
if (show->running == first->running &&
WSTATUS (show->status) == WSTATUS (first->status))
temp = "";
}
else
temp = (char *)NULL;
}
if (temp)
{
fprintf (stream, "%s", temp);
es = STRLEN (temp);
if (es == 0)
es = 2;
name_padding = LONGEST_SIGNAL_DESC - es;
fprintf (stream, "%*s", name_padding, "");
if ((WIFSTOPPED (show->status) == 0) &&
(WIFCONTINUED (show->status) == 0) &&
WIFCORED (show->status))
fprintf (stream, "(core dumped) ");
}
}
if (p != first && format)
fprintf (stream, "| ");
if (p->command)
fprintf (stream, "%s", p->command);
if (p == last && job_index >= 0)
{
temp = current_working_directory ();
if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0))
fprintf (stream, " &");
if (strcmp (temp, jobs[job_index]->wd) != 0)
fprintf (stream,
" (wd: %s)", polite_directory_format (jobs[job_index]->wd));
}
if (format || (p == last))
{
if (asynchronous_notification && interactive)
fprintf (stream, "\r\n");
else
fprintf (stream, "\n");
}
if (p == last)
break;
p = p->next;
}
fflush (stream);
}
static void
pretty_print_job (job_index, format, stream)
int job_index, format;
FILE *stream;
{
register PROCESS *p;
if (format == JLIST_PID_ONLY)
{
fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid);
return;
}
if (format == JLIST_CHANGED_ONLY)
{
if (IS_NOTIFIED (job_index))
return;
format = JLIST_STANDARD;
}
if (format != JLIST_NONINTERACTIVE)
fprintf (stream, "[%d]%c ", job_index + 1,
(job_index == current_job) ? '+':
(job_index == previous_job) ? '-' : ' ');
if (format == JLIST_NONINTERACTIVE)
format = JLIST_LONG;
p = jobs[job_index]->pipe;
print_pipeline (p, job_index, format, stream);
jobs[job_index]->flags |= J_NOTIFIED;
}
static int
print_job (job, format, state, job_index)
JOB *job;
int format, state, job_index;
{
if (state == -1 || (JOB_STATE)state == job->state)
pretty_print_job (job_index, format, stdout);
return (0);
}
void
list_one_job (job, format, ignore, job_index)
JOB *job;
int format, ignore, job_index;
{
pretty_print_job (job_index, format, stdout);
}
void
list_stopped_jobs (format)
int format;
{
cleanup_dead_jobs ();
map_over_jobs (print_job, format, (int)JSTOPPED);
}
void
list_running_jobs (format)
int format;
{
cleanup_dead_jobs ();
map_over_jobs (print_job, format, (int)JRUNNING);
}
void
list_all_jobs (format)
int format;
{
cleanup_dead_jobs ();
map_over_jobs (print_job, format, -1);
}
pid_t
make_child (command, async_p)
char *command;
int async_p;
{
sigset_t set, oset;
pid_t pid;
sigemptyset (&set);
sigaddset (&set, SIGCHLD);
sigaddset (&set, SIGINT);
sigemptyset (&oset);
sigprocmask (SIG_BLOCK, &set, &oset);
making_children ();
#if defined (BUFFERED_INPUT)
if (default_buffered_input != -1 &&
(!async_p || default_buffered_input > 0))
sync_buffered_stream (default_buffered_input);
#endif
if ((pid = fork ()) < 0)
{
sys_error ("fork");
terminate_current_pipeline ();
if (the_pipeline)
kill_current_pipeline ();
throw_to_top_level ();
}
if (pid == 0)
{
pid_t mypid;
mypid = getpid ();
#if defined (BUFFERED_INPUT)
unset_bash_input (0);
#endif
sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
if (job_control)
{
if (pipeline_pgrp == 0)
pipeline_pgrp = mypid;
if (pipeline_pgrp == shell_pgrp)
ignore_tty_job_signals ();
else
default_tty_job_signals ();
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 == mypid)
{
#endif
if (async_p == 0 && pipeline_pgrp != shell_pgrp)
give_terminal_to (pipeline_pgrp, 0);
#if defined (PGRP_PIPE)
pipe_read (pgrp_pipe);
}
#endif
}
else
{
if (pipeline_pgrp == 0)
pipeline_pgrp = shell_pgrp;
default_tty_job_signals ();
}
#if defined (PGRP_PIPE)
pipe_close (pgrp_pipe);
#endif
if (async_p)
last_asynchronous_pid = getpid ();
}
else
{
if (job_control)
{
if (pipeline_pgrp == 0)
{
pipeline_pgrp = pid;
}
setpgid (pid, pipeline_pgrp);
}
else
{
if (pipeline_pgrp == 0)
pipeline_pgrp = shell_pgrp;
}
add_process (command, pid);
if (async_p)
last_asynchronous_pid = pid;
last_made_pid = pid;
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
}
return (pid);
}
void
ignore_tty_job_signals ()
{
set_signal_handler (SIGTSTP, SIG_IGN);
set_signal_handler (SIGTTIN, SIG_IGN);
set_signal_handler (SIGTTOU, SIG_IGN);
}
void
default_tty_job_signals ()
{
set_signal_handler (SIGTSTP, SIG_DFL);
set_signal_handler (SIGTTIN, SIG_DFL);
set_signal_handler (SIGTTOU, SIG_DFL);
}
static TTYSTRUCT shell_tty_info;
#if defined (NEW_TTY_DRIVER)
static struct tchars shell_tchars;
static struct ltchars shell_ltchars;
#endif
#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT)
static int ttspeeds[] =
{
0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
1800, 2400, 4800, 9600, 19200, 38400
};
static void
draino (fd, ospeed)
int fd, ospeed;
{
register int delay = ttspeeds[ospeed];
int n;
if (!delay)
return;
while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n)
{
if (n > (delay / 100))
{
struct timeval tv;
n *= 10;
tv.tv_sec = n / delay;
tv.tv_usec = ((n % delay) * 1000000) / delay;
select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv);
}
else
break;
}
}
#endif
#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr)
int
get_tty_state ()
{
int tty;
tty = input_tty ();
if (tty != -1)
{
#if defined (NEW_TTY_DRIVER)
ioctl (tty, TIOCGETP, &shell_tty_info);
ioctl (tty, TIOCGETC, &shell_tchars);
ioctl (tty, TIOCGLTC, &shell_ltchars);
#endif
#if defined (TERMIO_TTY_DRIVER)
ioctl (tty, TCGETA, &shell_tty_info);
#endif
#if defined (TERMIOS_TTY_DRIVER)
if (tcgetattr (tty, &shell_tty_info) < 0)
{
#if 0
if (interactive)
sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level);
#endif
return -1;
}
#endif
if (check_window_size)
get_new_window_size (0);
}
return 0;
}
int
set_tty_state ()
{
int tty;
tty = input_tty ();
if (tty != -1)
{
#if defined (NEW_TTY_DRIVER)
# if defined (DRAIN_OUTPUT)
draino (tty, shell_tty_info.sg_ospeed);
# endif
ioctl (tty, TIOCSETN, &shell_tty_info);
ioctl (tty, TIOCSETC, &shell_tchars);
ioctl (tty, TIOCSLTC, &shell_ltchars);
#endif
#if defined (TERMIO_TTY_DRIVER)
ioctl (tty, TCSETAW, &shell_tty_info);
#endif
#if defined (TERMIOS_TTY_DRIVER)
if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
{
if (interactive)
sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level);
return -1;
}
#endif
}
return 0;
}
static pid_t
find_last_pid (job, block)
int job;
int block;
{
register PROCESS *p;
sigset_t set, oset;
if (block)
BLOCK_CHILD (set, oset);
p = jobs[job]->pipe;
while (p->next != jobs[job]->pipe)
p = p->next;
if (block)
UNBLOCK_CHILD (oset);
return (p->pid);
}
int
wait_for_single_pid (pid)
pid_t pid;
{
register PROCESS *child;
sigset_t set, oset;
int r, job;
BLOCK_CHILD (set, oset);
child = find_pipeline (pid, 0, (int *)NULL);
UNBLOCK_CHILD (oset);
if (child == 0)
{
internal_error ("wait: pid %ld is not a child of this shell", (long)pid);
return (127);
}
r = wait_for (pid);
BLOCK_CHILD (set, oset);
job = find_job (pid, 0);
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, r, waited_for;
sigset_t set, oset;
pid_t pid;
for (waited_for = 0;;)
{
BLOCK_CHILD (set, oset);
for (i = 0; i < job_slots; i++)
if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
break;
if (i == job_slots)
{
UNBLOCK_CHILD (oset);
break;
}
pid = find_last_pid (i, 0);
UNBLOCK_CHILD (oset);
QUIT;
errno = 0;
r = wait_for_single_pid (pid);
if (r == -1)
{
if (errno == ECHILD)
mark_all_jobs_as_dead ();
}
else
waited_for++;
}
mark_dead_jobs_as_notified (1);
cleanup_dead_jobs ();
}
#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids
static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;
static void
restore_sigint_handler ()
{
if (old_sigint_handler != INVALID_SIGNAL_HANDLER)
{
set_signal_handler (SIGINT, old_sigint_handler);
old_sigint_handler = INVALID_SIGNAL_HANDLER;
}
}
static int wait_sigint_received;
static sighandler
wait_sigint_handler (sig)
int sig;
{
SigHandler *sigint_handler;
if (interrupt_immediately ||
(this_shell_builtin && this_shell_builtin == wait_builtin))
{
last_command_exit_value = EXECUTION_FAILURE;
restore_sigint_handler ();
if (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);
wait_signal_received = SIGINT;
longjmp (wait_intr_buf, 1);
}
ADDINTERRUPT;
QUIT;
}
wait_sigint_received = 1;
SIGRETURN (0);
}
static int
process_exit_status (status)
WAIT status;
{
if (WIFSIGNALED (status))
return (128 + WTERMSIG (status));
else if (WIFSTOPPED (status) == 0)
return (WEXITSTATUS (status));
else
return (EXECUTION_SUCCESS);
}
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 (p->status);
}
static int
job_exit_status (job)
int job;
{
return (process_exit_status (raw_job_exit_status (job)));
}
#define FIND_CHILD(pid, child) \
do \
{ \
child = find_pipeline (pid, 0, (int *)NULL); \
if (child == 0) \
{ \
give_terminal_to (shell_pgrp, 0); \
UNBLOCK_CHILD (oset); \
internal_error ("wait_for: No record of process %ld", (long)pid); \
restore_sigint_handler (); \
return (termination_state = 127); \
} \
} \
while (0)
int
wait_for (pid)
pid_t pid;
{
int job, termination_state, r;
WAIT s;
register PROCESS *child;
sigset_t set, oset;
register PROCESS *p;
BLOCK_CHILD (set, oset);
wait_sigint_received = 0;
if (job_control == 0)
old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
termination_state = last_command_exit_value;
if (interactive && job_control == 0)
QUIT;
job = NO_JOB;
do
{
FIND_CHILD (pid, child);
if (job == NO_JOB)
job = find_job (pid, 0);
if (child->running || (job != NO_JOB && RUNNING (job)))
{
#if defined (WAITPID_BROKEN)
sigset_t suspend_set;
sigemptyset (&suspend_set);
sigsuspend (&suspend_set);
#else
# if defined (MUST_UNBLOCK_CHLD)
struct sigaction act, oact;
sigset_t nullset, chldset;
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;
sigaction (SIGCHLD, &act, &oact);
# endif
queue_sigchld = 1;
r = waitchld (pid, 1);
# if defined (MUST_UNBLOCK_CHLD)
sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
# endif
queue_sigchld = 0;
if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
{
termination_state = -1;
goto wait_for_return;
}
if (r == -1 && errno == ECHILD)
{
child->running = PS_DONE;
child->status = 0;
if (job != NO_JOB)
jobs[job]->state = JDEAD;
}
#endif
}
if (interactive && job_control == 0)
QUIT;
}
while (child->running || (job != NO_JOB && RUNNING (job)));
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))
{
#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 (job != NO_JOB)
{
if (interactive_shell && subshell_environment == 0)
{
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 (check_window_size &&
(job == current_job || IS_FOREGROUND (job)))
get_new_window_size (0);
}
else
get_tty_state ();
if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) &&
WIFSIGNALED (s) && WTERMSIG (s) == SIGINT)
{
if (signal_is_trapped (SIGINT) == 0 && loop_level)
ADDINTERRUPT;
else
{
putchar ('\n');
fflush (stdout);
}
}
}
if (DEADJOB (job) && IS_FOREGROUND (job) )
setjstatus (job);
notify_and_cleanup ();
}
wait_for_return:
UNBLOCK_CHILD (oset);
restore_sigint_handler ();
return (termination_state);
}
int
wait_for_job (job)
int job;
{
pid_t pid;
int r;
sigset_t set, oset;
BLOCK_CHILD(set, oset);
if (JOBSTATE (job) == JSTOPPED)
internal_warning ("wait_for_job: job %d is stopped", job+1);
pid = find_last_pid (job, 0);
UNBLOCK_CHILD(oset);
r = wait_for (pid);
BLOCK_CHILD (set, oset);
if (job != NO_JOB && jobs[job] && DEADJOB (job))
jobs[job]->flags |= J_NOTIFIED;
UNBLOCK_CHILD (oset);
return r;
}
void
notify_and_cleanup ()
{
if (jobs_list_frozen)
return;
if (interactive || interactive_shell == 0 || sourcelevel)
notify_of_job_status ();
cleanup_dead_jobs ();
}
void
reap_dead_jobs ()
{
mark_dead_jobs_as_notified (0);
cleanup_dead_jobs ();
}
static int
most_recent_job_in_state (job, state)
int job;
JOB_STATE state;
{
register int i, result;
sigset_t set, oset;
BLOCK_CHILD (set, oset);
for (result = NO_JOB, i = job - 1; i >= 0; i--)
{
if (jobs[i] && (JOBSTATE (i) == state))
{
result = i;
break;
}
}
UNBLOCK_CHILD (oset);
return (result);
}
static int
job_last_stopped (job)
int job;
{
return (most_recent_job_in_state (job, JSTOPPED));
}
static int
job_last_running (job)
int job;
{
return (most_recent_job_in_state (job, JRUNNING));
}
static void
set_current_job (job)
int job;
{
int candidate;
if (current_job != job)
{
previous_job = current_job;
current_job = job;
}
if (previous_job != current_job &&
previous_job != NO_JOB &&
jobs[previous_job] &&
STOPPED (previous_job))
return;
candidate = NO_JOB;
if (STOPPED (current_job))
{
candidate = job_last_stopped (current_job);
if (candidate != NO_JOB)
{
previous_job = candidate;
return;
}
}
candidate = RUNNING (current_job) ? job_last_running (current_job)
: job_last_running (job_slots);
if (candidate != NO_JOB)
{
previous_job = candidate;
return;
}
previous_job = current_job;
}
static void
reset_current ()
{
int candidate;
if (job_slots && current_job != NO_JOB && jobs[current_job] && STOPPED (current_job))
candidate = current_job;
else
{
candidate = NO_JOB;
if (previous_job != NO_JOB && jobs[previous_job] && STOPPED (previous_job))
candidate = previous_job;
if (candidate == NO_JOB)
candidate = job_last_stopped (job_slots);
if (candidate == NO_JOB)
candidate = job_last_running (job_slots);
}
if (candidate != NO_JOB)
set_current_job (candidate);
else
current_job = previous_job = NO_JOB;
}
static void
set_job_running (job)
int job;
{
register PROCESS *p;
p = jobs[job]->pipe;
do
{
if (WIFSTOPPED (p->status))
p->running = PS_RUNNING;
p = p->next;
}
while (p != jobs[job]->pipe);
JOBSTATE (job) = JRUNNING;
}
int
start_job (job, foreground)
int job, foreground;
{
register PROCESS *p;
int already_running;
sigset_t set, oset;
char *wd;
static TTYSTRUCT save_stty;
BLOCK_CHILD (set, oset);
if (DEADJOB (job))
{
internal_error ("%s: job has terminated", this_command_name);
UNBLOCK_CHILD (oset);
return (-1);
}
already_running = RUNNING (job);
if (foreground == 0 && already_running)
{
internal_error ("%s: bg background job?", this_command_name);
UNBLOCK_CHILD (oset);
return (-1);
}
wd = current_working_directory ();
jobs[job]->flags &= ~J_NOTIFIED;
if (foreground)
{
set_current_job (job);
jobs[job]->flags |= J_FOREGROUND;
}
p = jobs[job]->pipe;
if (foreground == 0)
fprintf (stderr, "[%d]%c ", job + 1,
(job == current_job) ? '+': ((job == previous_job) ? '-' : ' '));
do
{
fprintf (stderr, "%s%s",
p->command ? p->command : "",
p->next != jobs[job]->pipe? " | " : "");
p = p->next;
}
while (p != jobs[job]->pipe);
if (foreground == 0)
fprintf (stderr, " &");
if (strcmp (wd, jobs[job]->wd) != 0)
fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd));
fprintf (stderr, "\n");
if (already_running == 0)
set_job_running (job);
if (foreground)
{
get_tty_state ();
save_stty = shell_tty_info;
if (IS_JOBCONTROL (job))
give_terminal_to (jobs[job]->pgrp, 0);
}
else
jobs[job]->flags &= ~J_FOREGROUND;
if (already_running == 0)
{
jobs[job]->flags |= J_NOTIFIED;
killpg (jobs[job]->pgrp, SIGCONT);
}
if (foreground)
{
pid_t pid;
int s;
pid = find_last_pid (job, 0);
UNBLOCK_CHILD (oset);
s = wait_for (pid);
shell_tty_info = save_stty;
set_tty_state ();
return (s);
}
else
{
reset_current ();
UNBLOCK_CHILD (oset);
return (0);
}
}
int
kill_pid (pid, sig, group)
pid_t pid;
int sig, group;
{
register PROCESS *p;
int job, result;
sigset_t set, oset;
result = EXECUTION_SUCCESS;
if (group)
{
BLOCK_CHILD (set, oset);
p = find_pipeline (pid, 0, &job);
if (job != NO_JOB)
{
jobs[job]->flags &= ~J_NOTIFIED;
if (jobs[job]->pgrp == shell_pgrp)
{
p = jobs[job]->pipe;
do
{
kill (p->pid, sig);
if (p->running == PS_DONE && (sig == SIGTERM || sig == SIGHUP))
kill (p->pid, SIGCONT);
p = p->next;
}
while (p != jobs[job]->pipe);
}
else
{
result = killpg (jobs[job]->pgrp, sig);
if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP))
killpg (jobs[job]->pgrp, SIGCONT);
if (p && STOPPED (job) && (sig == SIGCONT))
{
set_job_running (job);
jobs[job]->flags &= ~J_FOREGROUND;
jobs[job]->flags |= J_NOTIFIED;
}
}
}
else
result = killpg (pid, sig);
UNBLOCK_CHILD (oset);
}
else
result = kill (pid, sig);
return (result);
}
static sighandler
sigchld_handler (sig)
int sig;
{
int n, oerrno;
oerrno = errno;
REINSTALL_SIGCHLD_HANDLER;
sigchld++;
n = 0;
if (queue_sigchld == 0)
n = waitchld (-1, 0);
errno = oerrno;
SIGRETURN (n);
}
static int
waitchld (wpid, block)
pid_t wpid;
int block;
{
WAIT status;
PROCESS *child;
pid_t pid;
int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
call_set_current = children_exited = 0;
last_stopped_job = NO_JOB;
do
{
waitpid_flags = (job_control && subshell_environment == 0)
? (WUNTRACED|WCONTINUED)
: 0;
if (sigchld || block == 0)
waitpid_flags |= WNOHANG;
pid = WAITPID (-1, &status, waitpid_flags);
if (sigchld > 0 && (waitpid_flags & WNOHANG))
sigchld--;
if (pid < 0 && errno == ECHILD)
{
if (children_exited == 0)
return -1;
else
break;
}
if (pid <= 0)
continue;
if (WIFCONTINUED(status) == 0)
children_exited++;
child = find_pipeline (pid, 1, &job);
if (child == 0)
continue;
while (child->pid != pid)
child = child->next;
child->status = status;
child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE;
if (job == NO_JOB)
continue;
call_set_current += set_job_status_and_cleanup (job);
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 (call_set_current)
{
if (last_stopped_job != NO_JOB)
set_current_job (last_stopped_job);
else
reset_current ();
}
if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
trap_list[SIGCHLD] != (char *)IGNORE_SIG)
run_sigchld_trap (children_exited);
if (asynchronous_notification && interactive)
notify_of_job_status ();
return (children_exited);
}
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;
job_state = any_stopped = any_tstped = 0;
do
{
job_state |= child->running;
if (child->running == PS_DONE && (WIFSTOPPED (child->status)))
{
any_stopped = 1;
any_tstped |= interactive && job_control &&
(WSTOPSIG (child->status) == SIGTSTP);
}
child = child->next;
}
while (child != jobs[job]->pipe);
if (job_state != 0 && JOBSTATE(job) != JSTOPPED)
return 0;
if (any_stopped)
{
jobs[job]->state = JSTOPPED;
jobs[job]->flags &= ~J_FOREGROUND;
call_set_current++;
if (any_tstped && loop_level)
breaking = loop_level;
}
else if (job_state != 0)
{
jobs[job]->state = JRUNNING;
call_set_current++;
}
else
{
jobs[job]->state = JDEAD;
#if 0
if (IS_FOREGROUND (job))
setjstatus (job);
#endif
if (jobs[job]->j_cleanup)
{
(*jobs[job]->j_cleanup) (jobs[job]->cleanarg);
jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL;
}
}
if (jobs[job]->state == JDEAD)
{
if (wait_sigint_received && interactive_shell == 0 &&
WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
signal_is_trapped (SIGINT))
{
int old_frozen;
wait_sigint_received = 0;
last_command_exit_value = process_exit_status (child->status);
old_frozen = jobs_list_frozen;
jobs_list_frozen = 1;
tstatus = maybe_call_trap_handler (SIGINT);
jobs_list_frozen = old_frozen;
}
else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
{
int old_frozen;
wait_sigint_received = 0;
if (signal_is_trapped (SIGINT))
last_command_exit_value = process_exit_status (child->status);
old_frozen = jobs_list_frozen;
jobs_list_frozen = 1;
tstatus = maybe_call_trap_handler (SIGINT);
jobs_list_frozen = old_frozen;
if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
{
temp_handler = old_sigint_handler;
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);
}
}
}
return call_set_current;
}
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;
set_pipestatus_array (pstatuses, i);
#endif
}
static void
run_sigchld_trap (nchild)
int nchild;
{
char *trap_command;
int i;
trap_command = savestring (trap_list[SIGCHLD]);
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);
add_unwind_protect ((Function *)xfree, trap_command);
add_unwind_protect ((Function *)maybe_set_sigchld_trap, trap_command);
subst_assign_varlist = (WORD_LIST *)NULL;
the_pipeline = (PROCESS *)NULL;
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");
}
static void
notify_of_job_status ()
{
register int job, termsig;
char *dir;
sigset_t set, oset;
WAIT s;
if (jobs == 0 || job_slots == 0)
return;
if (old_ttou != 0)
{
sigemptyset (&set);
sigaddset (&set, SIGCHLD);
sigaddset (&set, SIGTTOU);
sigemptyset (&oset);
sigprocmask (SIG_BLOCK, &set, &oset);
}
else
queue_sigchld++;
for (job = 0, dir = (char *)NULL; job < job_slots; job++)
{
if (jobs[job] && IS_NOTIFIED (job) == 0)
{
s = raw_job_exit_status (job);
termsig = WTERMSIG (s);
if (startup_state == 0 && WIFSIGNALED (s) == 0 &&
((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job)))
continue;
if ((job_control == 0 && interactive_shell) || startup_state == 2)
{
if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid)))
jobs[job]->flags |= J_NOTIFIED;
continue;
}
switch (JOBSTATE (job))
{
case JDEAD:
if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
termsig != SIGINT &&
#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);
}
else if (IS_FOREGROUND (job))
{
#if !defined (DONT_REPORT_SIGPIPE)
if (termsig && WIFSIGNALED (s) && termsig != SIGINT)
#else
if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE)
#endif
{
fprintf (stderr, "%s", strsignal (termsig));
if (WIFCORED (s))
fprintf (stderr, " (core dumped)");
fprintf (stderr, "\n");
}
}
else
{
if (dir == 0)
dir = current_working_directory ();
pretty_print_job (job, JLIST_STANDARD, stderr);
if (dir && strcmp (dir, jobs[job]->wd) != 0)
fprintf (stderr,
"(wd now: %s)\n", polite_directory_format (dir));
}
jobs[job]->flags |= J_NOTIFIED;
break;
case JSTOPPED:
fprintf (stderr, "\n");
if (dir == 0)
dir = current_working_directory ();
pretty_print_job (job, JLIST_STANDARD, stderr);
if (dir && (strcmp (dir, jobs[job]->wd) != 0))
fprintf (stderr,
"(wd now: %s)\n", polite_directory_format (dir));
jobs[job]->flags |= J_NOTIFIED;
break;
case JRUNNING:
case JMIXED:
break;
default:
programming_error ("notify_of_job_status");
}
}
}
if (old_ttou != 0)
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
else
queue_sigchld--;
}
int
initialize_job_control (force)
int force;
{
shell_pgrp = getpgid (0);
if (shell_pgrp == -1)
{
sys_error ("initialize_job_control: getpgrp failed");
exit (1);
}
if (interactive == 0)
{
job_control = 0;
original_pgrp = NO_PID;
shell_tty = fileno (stderr);
}
else
{
shell_tty = dup (fileno (stderr));
shell_tty = move_to_high_fd (shell_tty, 1, -1);
if (shell_pgrp == 0)
{
shell_pgrp = getpid ();
setpgid (0, shell_pgrp);
tcsetpgrp (shell_tty, shell_pgrp);
}
while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1)
{
if (shell_pgrp != terminal_pgrp)
{
SigHandler *ottin;
ottin = set_signal_handler(SIGTTIN, SIG_DFL);
kill (0, SIGTTIN);
set_signal_handler (SIGTTIN, ottin);
continue;
}
break;
}
if (set_new_line_discipline (shell_tty) < 0)
{
sys_error ("initialize_job_control: line discipline");
job_control = 0;
}
else
{
original_pgrp = shell_pgrp;
shell_pgrp = getpid ();
if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0))
{
sys_error ("initialize_job_control: setpgid");
shell_pgrp = original_pgrp;
}
job_control = 1;
if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
{
if (give_terminal_to (shell_pgrp, 0) < 0)
{
setpgid (0, original_pgrp);
shell_pgrp = original_pgrp;
job_control = 0;
}
}
}
if (job_control == 0)
internal_error ("no job control in this shell");
}
if (shell_tty != fileno (stderr))
SET_CLOSE_ON_EXEC (shell_tty);
set_signal_handler (SIGCHLD, sigchld_handler);
change_flag ('m', job_control ? '-' : '+');
if (interactive)
get_tty_state ();
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
static int
set_new_line_discipline (tty)
int tty;
{
#if defined (NEW_TTY_DRIVER)
int ldisc;
if (ioctl (tty, TIOCGETD, &ldisc) < 0)
return (-1);
if (ldisc != NTTYDISC)
{
ldisc = NTTYDISC;
if (ioctl (tty, TIOCSETD, &ldisc) < 0)
return (-1);
}
return (0);
#endif
#if defined (TERMIO_TTY_DRIVER)
# if defined (TERMIO_LDISC) && (NTTYDISC)
if (ioctl (tty, TCGETA, &shell_tty_info) < 0)
return (-1);
if (shell_tty_info.c_line != NTTYDISC)
{
shell_tty_info.c_line = NTTYDISC;
if (ioctl (tty, TCSETAW, &shell_tty_info) < 0)
return (-1);
}
# endif
return (0);
#endif
#if defined (TERMIOS_TTY_DRIVER)
# if defined (TERMIOS_LDISC) && defined (NTTYDISC)
if (tcgetattr (tty, &shell_tty_info) < 0)
return (-1);
if (shell_tty_info.c_line != NTTYDISC)
{
shell_tty_info.c_line = NTTYDISC;
if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
return (-1);
}
# endif
return (0);
#endif
#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER)
return (-1);
#endif
}
#if defined (TIOCGWINSZ) && defined (SIGWINCH)
static void
get_new_window_size (from_sig)
int from_sig;
{
struct winsize win;
if ((ioctl (shell_tty, TIOCGWINSZ, &win) == 0) &&
win.ws_row > 0 && win.ws_col > 0)
{
#if defined (aixpc)
shell_tty_info.c_winsize = win;
#endif
sh_set_lines_and_columns (win.ws_row, win.ws_col);
#if defined (READLINE)
rl_set_screen_size (win.ws_row, win.ws_col);
#endif
}
}
static sighandler
sigwinch_sighandler (sig)
int sig;
{
#if defined (MUST_REINSTALL_SIGHANDLERS)
set_signal_handler (SIGWINCH, sigwinch_sighandler);
#endif
get_new_window_size (1);
SIGRETURN (0);
}
#else
static void
get_new_window_size (from_sig)
int from_sig;
{
}
#endif
void
set_sigwinch_handler ()
{
#if defined (TIOCGWINSZ) && defined (SIGWINCH)
old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler);
#endif
}
void
unset_sigwinch_handler ()
{
#if defined (TIOCGWINSZ) && defined (SIGWINCH)
set_signal_handler (SIGWINCH, old_winch);
#endif
}
void
initialize_job_signals ()
{
if (interactive)
{
set_signal_handler (SIGINT, sigint_sighandler);
set_signal_handler (SIGTSTP, SIG_IGN);
set_signal_handler (SIGTTOU, SIG_IGN);
set_signal_handler (SIGTTIN, SIG_IGN);
set_sigwinch_handler ();
}
else if (job_control)
{
old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler);
old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler);
old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
}
}
static sighandler
sigcont_sighandler (sig)
int sig;
{
initialize_job_signals ();
set_signal_handler (SIGCONT, old_cont);
kill (getpid (), SIGCONT);
SIGRETURN (0);
}
static sighandler
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, sigcont_sighandler);
give_terminal_to (shell_pgrp, 0);
kill (getpid (), sig);
SIGRETURN (0);
}
int
give_terminal_to (pgrp, force)
pid_t pgrp;
int force;
{
sigset_t set, oset;
int r;
r = 0;
if (job_control || force)
{
sigemptyset (&set);
sigaddset (&set, SIGTTOU);
sigaddset (&set, SIGTTIN);
sigaddset (&set, SIGTSTP);
sigaddset (&set, SIGCHLD);
sigemptyset (&oset);
sigprocmask (SIG_BLOCK, &set, &oset);
if (tcsetpgrp (shell_tty, pgrp) < 0)
{
#if 0
sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld",
shell_tty, (long)getpid(), (long)pgrp);
#endif
r = -1;
}
else
terminal_pgrp = pgrp;
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
}
return r;
}
void
delete_all_jobs (running_only)
int running_only;
{
register int i;
sigset_t set, oset;
BLOCK_CHILD (set, oset);
if (job_slots)
{
current_job = previous_job = NO_JOB;
for (i = 0; i < job_slots; i++)
if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
delete_job (i, 1);
if (running_only == 0)
{
free ((char *)jobs);
job_slots = 0;
}
}
UNBLOCK_CHILD (oset);
}
void
nohup_all_jobs (running_only)
int running_only;
{
register int i;
sigset_t set, oset;
BLOCK_CHILD (set, oset);
if (job_slots)
{
for (i = 0; i < job_slots; i++)
if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
nohup_job (i);
}
UNBLOCK_CHILD (oset);
}
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_all_jobs_as_dead ()
{
register int i;
sigset_t set, oset;
if (job_slots == 0)
return;
BLOCK_CHILD (set, oset);
for (i = 0; i < job_slots; i++)
if (jobs[i])
jobs[i]->state = JDEAD;
UNBLOCK_CHILD (oset);
}
static void
mark_dead_jobs_as_notified (force)
int force;
{
register int i, ndead;
sigset_t set, oset;
if (job_slots == 0)
return;
BLOCK_CHILD (set, oset);
if (force)
{
for (i = 0; i < job_slots; i++)
{
if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
jobs[i]->flags |= J_NOTIFIED;
}
UNBLOCK_CHILD (oset);
return;
}
for (i = ndead = 0; i < job_slots; i++)
{
if (jobs[i] && DEADJOB (i))
ndead++;
}
if (child_max < 0)
child_max = getmaxchild ();
if (child_max < 0)
child_max = DEFAULT_CHILD_MAX;
if (ndead <= child_max)
{
UNBLOCK_CHILD (oset);
return;
}
for (i = 0; i < job_slots; i++)
{
if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
{
jobs[i]->flags |= J_NOTIFIED;
if (--ndead <= child_max)
break;
}
}
UNBLOCK_CHILD (oset);
}
void
unfreeze_jobs_list ()
{
jobs_list_frozen = 0;
}
int
set_job_control (arg)
int arg;
{
int old;
old = job_control;
job_control = arg;
return (old);
}
void
without_job_control ()
{
stop_making_children ();
start_pipeline ();
delete_all_jobs (0);
set_job_control (0);
}
void
end_job_control ()
{
if (interactive_shell)
{
terminate_stopped_jobs ();
if (original_pgrp >= 0)
give_terminal_to (original_pgrp, 1);
}
if (original_pgrp >= 0)
setpgid (0, original_pgrp);
}
void
restart_job_control ()
{
if (shell_tty != -1)
close (shell_tty);
initialize_job_control (0);
}
void
set_sigchld_handler ()
{
set_signal_handler (SIGCHLD, sigchld_handler);
}
#if defined (PGRP_PIPE)
static void
pipe_read (pp)
int *pp;
{
char ch;
if (pp[1] >= 0)
{
close (pp[1]);
pp[1] = -1;
}
if (pp[0] >= 0)
{
while (read (pp[0], &ch, 1) == -1 && errno == EINTR)
;
}
}
static void
pipe_close (pp)
int *pp;
{
if (pp[0] >= 0)
close (pp[0]);
if (pp[1] >= 0)
close (pp[1]);
pp[0] = pp[1] = -1;
}
void
close_pgrp_pipe ()
{
pipe_close (pgrp_pipe);
}
#endif