1722 lines
38 KiB
C
1722 lines
38 KiB
C
/* The thing that makes children, remembers them, and contains wait loops. */
|
|
|
|
/* Copyright (C) 1989 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Bash, the Bourne Again SHell.
|
|
|
|
Bash is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 1, or (at your option) any later
|
|
version.
|
|
|
|
Bash is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
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. */
|
|
|
|
/* Something that can be ignored. */
|
|
#define IGNORE_ARG (char *)0
|
|
|
|
#include "config.h"
|
|
|
|
#ifndef JOB_CONTROL
|
|
#include "nojobs.c"
|
|
#else
|
|
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/file.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/param.h>
|
|
|
|
/* Terminal handling stuff, to save and restore tty state. */
|
|
#define NEW_TTY_DRIVER
|
|
|
|
#if defined(SYSV) || defined(hpux) || defined(ALTOS)
|
|
#undef NEW_TTY_DRIVER
|
|
#endif /* SYSV || hpux || ALTOS */
|
|
|
|
#ifdef NEW_TTY_DRIVER
|
|
#include <sgtty.h>
|
|
#else
|
|
#include <termio.h>
|
|
#endif /* NEW_TTY_DRIVER */
|
|
|
|
/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */
|
|
|
|
#ifdef hpux
|
|
#include <bsdtty.h>
|
|
#endif /* hpux */
|
|
|
|
#include "shell.h"
|
|
#include "jobs.h"
|
|
|
|
/* Not all systems define errno in errno.h. */
|
|
extern int errno;
|
|
|
|
#ifndef sigmask
|
|
#define sigmask(x) (1 << ((x)-1))
|
|
#endif
|
|
|
|
#ifndef SIGABRT
|
|
#define SIGABRT SIGIOT
|
|
#endif
|
|
|
|
#ifndef SIGCHLD
|
|
#define SIGCHLD SIGCLD
|
|
#endif
|
|
|
|
/* The array of known jobs. */
|
|
JOB **jobs = (JOB **)NULL;
|
|
|
|
/* The number of slots currently allocated to JOBS. */
|
|
int job_slots = 0;
|
|
|
|
/* The number of additional slots to allocate when we run out. */
|
|
#define JOB_SLOTS 5
|
|
|
|
/* The controlling tty for this shell. */
|
|
int shell_tty;
|
|
|
|
/* The shell's process group. */
|
|
int shell_pgrp = -1;
|
|
|
|
/* The terminal's process group. */
|
|
int terminal_pgrp = -1;
|
|
|
|
/* The process group of the shell's parent. */
|
|
int original_pgrp = -1;
|
|
|
|
/* The process group of the pipeline currently being made. */
|
|
int pipeline_pgrp = 0;
|
|
|
|
/* The job which is current; i.e. the one that `%+' stands for. */
|
|
int current_job = NO_JOB;
|
|
|
|
/* The previous job; i.e. the one that `%-' stands for. */
|
|
int previous_job = NO_JOB;
|
|
|
|
/* Last child made by the shell. */
|
|
int last_made_pid = -1;
|
|
|
|
/* Pid of the last asynchronous child. */
|
|
int last_asynchronous_pid = -1;
|
|
|
|
/* Non-zero allows asynchronous job notification. If not set,
|
|
then job state notification only takes place just before a
|
|
prompt is printed. */
|
|
int asynchronous_notification = 0;
|
|
|
|
#ifndef hpux
|
|
/* The total amount of system time spent running processes for me. */
|
|
struct timeval total_systime = {0, 0};
|
|
long system_minutes_used = 0;
|
|
int system_seconds_used = 0;
|
|
|
|
/* The total amount of user time spent running processes for me. */
|
|
struct timeval total_usertime = {0, 0};
|
|
long user_minutes_used = 0;
|
|
int user_seconds_used = 0;
|
|
#endif /* hpux */
|
|
|
|
/* The pipeline currently being built. */
|
|
PROCESS *the_pipeline = (PROCESS *)NULL;
|
|
|
|
/* If this is non-zero, do job control. */
|
|
int job_control = 1;
|
|
|
|
/* Call this when you start making children. */
|
|
int already_making_children = 0;
|
|
|
|
/* These are definitions to map POSIX 1003.1 functions onto existing BSD
|
|
library functions and system calls. */
|
|
|
|
#define setpgid(pid, pgrp) setpgrp (pid, pgrp)
|
|
#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp))
|
|
|
|
tcgetpgrp (fd)
|
|
int fd;
|
|
{
|
|
int pgrp;
|
|
|
|
/* ioctl will handle setting errno correctly. */
|
|
if (ioctl (fd, TIOCGPGRP, &pgrp) < 0)
|
|
return (-1);
|
|
return (pgrp);
|
|
}
|
|
|
|
/* END of POISX 1003.1 definitions. */
|
|
|
|
making_children ()
|
|
{
|
|
if (already_making_children)
|
|
return;
|
|
|
|
already_making_children = 1;
|
|
start_pipeline ();
|
|
}
|
|
|
|
stop_making_children ()
|
|
{
|
|
already_making_children = 0;
|
|
}
|
|
|
|
/* Start building a pipeline. */
|
|
start_pipeline ()
|
|
{
|
|
if (the_pipeline)
|
|
{
|
|
discard_pipeline (the_pipeline);
|
|
the_pipeline = (PROCESS *)NULL;
|
|
pipeline_pgrp = 0;
|
|
}
|
|
}
|
|
|
|
/* Stop building a pipeline. Install the process list in the job array.
|
|
This returns the index of the newly installed job.
|
|
DEFERRED is a command structure to be executed upon satisfactory
|
|
execution exit of this pipeline. */
|
|
int
|
|
stop_pipeline (async, deferred)
|
|
int async;
|
|
COMMAND *deferred;
|
|
{
|
|
register int i, j;
|
|
int oldmask;
|
|
JOB *newjob = (JOB *)NULL;
|
|
char *get_string_value ();
|
|
|
|
oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
cleanup_dead_jobs ();
|
|
|
|
if (!job_slots)
|
|
{
|
|
jobs =
|
|
(JOB **)xmalloc ((1 + (job_slots = JOB_SLOTS)) * sizeof (JOB *));
|
|
|
|
/* Now blank out these new entries. */
|
|
for (i = 0; i < job_slots; i++)
|
|
jobs[i] = (JOB *)NULL;
|
|
}
|
|
|
|
/* Scan from the last slot backward, looking for the next free one. */
|
|
for (i = job_slots; i; i--)
|
|
if (jobs[i - 1])
|
|
break;
|
|
|
|
/* Do we need more room? */
|
|
if (i == job_slots)
|
|
{
|
|
jobs = (JOB **)realloc
|
|
(jobs, (1 + (job_slots += JOB_SLOTS)) * sizeof (JOB *));
|
|
|
|
for (j = i; j < job_slots; j++)
|
|
jobs[j] = (JOB *)NULL;
|
|
}
|
|
|
|
/* Add the current pipeline to the job list. */
|
|
if (the_pipeline)
|
|
{
|
|
extern int errno, sys_nerr;
|
|
extern char *sys_errlist[];
|
|
register PROCESS *p;
|
|
|
|
newjob = (JOB *)xmalloc (sizeof (JOB));
|
|
|
|
for (p = the_pipeline; p->next != the_pipeline; p = p->next);
|
|
p->next = (PROCESS *)NULL;
|
|
newjob->pipe = (PROCESS *)reverse_list (the_pipeline);
|
|
for (p = newjob->pipe; p->next; p = p->next);
|
|
p->next = newjob->pipe;
|
|
|
|
the_pipeline = (PROCESS *)NULL;
|
|
newjob->pgrp = pipeline_pgrp;
|
|
pipeline_pgrp = 0;
|
|
|
|
/* Flag to see if in another pgrp. */
|
|
newjob->job_control = job_control;
|
|
|
|
/* Set the state of this pipeline. */
|
|
{
|
|
register PROCESS *p = newjob->pipe;
|
|
register int any_alive = 0;
|
|
register int any_stopped = 0;
|
|
|
|
do
|
|
{
|
|
any_alive |= p->running;
|
|
any_stopped |= WIFSTOPPED (p->status);
|
|
p = p->next;
|
|
}
|
|
while (p != newjob->pipe);
|
|
|
|
if (any_alive)
|
|
{
|
|
newjob->state = JRUNNING;
|
|
}
|
|
else
|
|
{
|
|
if (any_stopped)
|
|
newjob->state = JSTOPPED;
|
|
else
|
|
newjob->state = JDEAD;
|
|
}
|
|
}
|
|
|
|
newjob->notified = 0;
|
|
|
|
newjob->wd = get_string_value ("PWD");
|
|
|
|
if (newjob->wd)
|
|
newjob->wd = savestring (newjob->wd);
|
|
else
|
|
newjob->wd = (char *)get_working_directory ("");
|
|
|
|
if (!(newjob->wd))
|
|
newjob->wd = savestring ("<no directory>");
|
|
|
|
newjob->deferred = deferred;
|
|
|
|
jobs[i] = newjob;
|
|
}
|
|
|
|
if (async)
|
|
{
|
|
if (newjob)
|
|
newjob->foreground = 0;
|
|
reset_current ();
|
|
}
|
|
else
|
|
{
|
|
if (newjob)
|
|
{
|
|
newjob->foreground = 1;
|
|
/*
|
|
* !!!!! NOTE !!!!! (chet@ins.cwru.edu)
|
|
*
|
|
* The currently-accepted job control wisdom says to set the
|
|
* terminal's process group n+1 times in an n-step pipeline:
|
|
* once in the parent and once in each child. This is where
|
|
* the parent gives it away.
|
|
*
|
|
*/
|
|
if (job_control && pipeline_pgrp)
|
|
give_terminal_to (pipeline_pgrp);
|
|
}
|
|
}
|
|
|
|
stop_making_children ();
|
|
sigsetmask (oldmask);
|
|
return (current_job);
|
|
}
|
|
|
|
/* Delete all DEAD jobs that the user had received notification about. */
|
|
cleanup_dead_jobs ()
|
|
{
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
register int i;
|
|
|
|
for (i = 0; i < job_slots; i++)
|
|
if (jobs[i] && JOBSTATE (i) == JDEAD && jobs[i]->notified)
|
|
delete_job (i);
|
|
|
|
sigsetmask (oldmask);
|
|
}
|
|
|
|
/* Delete the job at INDEX from the job list. */
|
|
delete_job (index)
|
|
int index;
|
|
{
|
|
register JOB *temp = jobs[index];
|
|
|
|
if (index == current_job || index == previous_job)
|
|
reset_current ();
|
|
|
|
jobs[index] = (JOB *)NULL;
|
|
|
|
free (temp->wd);
|
|
discard_pipeline (temp->pipe);
|
|
|
|
if (temp->deferred)
|
|
dispose_command (temp->deferred);
|
|
|
|
free (temp);
|
|
}
|
|
|
|
/* Get rid of the data structure associated with a process chain. */
|
|
discard_pipeline (chain)
|
|
register PROCESS *chain;
|
|
{
|
|
register PROCESS *this, *next;
|
|
|
|
this = chain;
|
|
do
|
|
{
|
|
next = this->next;
|
|
if (this->command)
|
|
free (this->command);
|
|
free (this);
|
|
this = next;
|
|
}
|
|
while (this != chain);
|
|
}
|
|
|
|
/* Add this process to the chain being built in the_pipeline.
|
|
NAME is the command string that will be exec'ed later.
|
|
PID is the process id of the child. */
|
|
add_process (name, pid)
|
|
char *name;
|
|
int pid;
|
|
{
|
|
PROCESS *t = (PROCESS *)xmalloc (sizeof (PROCESS));
|
|
|
|
t->next = the_pipeline;
|
|
t->pid = pid;
|
|
t->status.w_status = 0;
|
|
t->running = 1;
|
|
t->command = name;
|
|
the_pipeline = t;
|
|
|
|
if (!(t->next))
|
|
{
|
|
t->next = t;
|
|
}
|
|
else
|
|
{
|
|
register PROCESS *p = t->next;
|
|
|
|
while (p->next != t->next) p = p->next;
|
|
p->next = t;
|
|
}
|
|
}
|
|
|
|
/* Map FUNC over the list of jobs. If FUNC returns non-zero,
|
|
then it is time to stop mapping, and that is the return value
|
|
for map_over_jobs. FUNC is called with a JOB, arg1, arg2,
|
|
and INDEX. */
|
|
map_over_jobs (func, arg1, arg2)
|
|
Function *func;
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; i < job_slots; i++)
|
|
{
|
|
if (jobs[i])
|
|
{
|
|
int result = (*func)(jobs[i], arg1, arg2, i);
|
|
if (result)
|
|
return (result);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Return the pipeline that PID belongs to. Note that the pipeline
|
|
doesn't have to belong to a job. */
|
|
PROCESS *
|
|
find_pipeline (pid)
|
|
int pid;
|
|
{
|
|
int job;
|
|
|
|
/* See if this process is in the pipeline that we are building. */
|
|
if (the_pipeline)
|
|
{
|
|
register PROCESS *p = the_pipeline;
|
|
|
|
do
|
|
{
|
|
/* Return it if we found it. */
|
|
if (p->pid == pid)
|
|
return (p);
|
|
|
|
p = p->next;
|
|
}
|
|
while (p != the_pipeline);
|
|
}
|
|
|
|
job = find_job (pid);
|
|
|
|
if (job == NO_JOB)
|
|
return ((PROCESS *)NULL);
|
|
else
|
|
return (jobs[job]->pipe);
|
|
}
|
|
|
|
/* Return the job index that PID belongs to, or NO_JOB if it doesn't
|
|
belong to any job. */
|
|
int
|
|
find_job (pid)
|
|
int pid;
|
|
{
|
|
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)
|
|
return (i);
|
|
|
|
p = p->next;
|
|
}
|
|
while (p != jobs[i]->pipe);
|
|
}
|
|
}
|
|
|
|
return (NO_JOB);
|
|
}
|
|
|
|
/* Print descriptive information about the job with leader pid PID. */
|
|
describe_pid (pid)
|
|
int pid;
|
|
{
|
|
int job;
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
job = find_job (pid);
|
|
|
|
if (job != NO_JOB)
|
|
printf ("[%d] %d\n", job + 1, pid);
|
|
else
|
|
programming_error ("describe_pid: No such pid (%d)!\n", pid);
|
|
|
|
sigsetmask (oldmask);
|
|
}
|
|
|
|
|
|
/* This is the way to print out information on a job if you
|
|
know the index. FORMAT is:
|
|
|
|
0) [1]+ Running emacs
|
|
1) [1]+ 2378 Running emacs
|
|
-1) [1]+ 2378 emacs
|
|
|
|
0) [1]+ Stopped ls | more
|
|
1) [1]+ 2369 Stopped ls
|
|
2367 | more
|
|
*/
|
|
pretty_print_job (index, format, stream)
|
|
int index, format;
|
|
FILE *stream;
|
|
{
|
|
register PROCESS *p;
|
|
int first, oldmask;
|
|
union wait first_job_cond;
|
|
int name_padding;
|
|
|
|
oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
fprintf (stream, "[%d]%c ", index + 1,
|
|
(index == current_job) ? '+':
|
|
(index == previous_job) ? '-' : ' ');
|
|
|
|
first = 1;
|
|
p = jobs[index]->pipe;
|
|
|
|
do
|
|
{
|
|
if (!first && !format)
|
|
fprintf (stream, " |");
|
|
else if (!first)
|
|
fprintf (stream, " ");
|
|
|
|
if (format)
|
|
fprintf (stream, "%d", p->pid);
|
|
|
|
fprintf (stream, " ");
|
|
|
|
if (format > -1)
|
|
{
|
|
extern char *sys_siglist[];
|
|
union wait status;
|
|
char *temp = "Done";
|
|
|
|
if (JOBSTATE (index) == JSTOPPED && !format)
|
|
temp = "Stopped";
|
|
|
|
status = p->status;
|
|
if (p->running)
|
|
{
|
|
temp = "Running";
|
|
}
|
|
else
|
|
{
|
|
if (status.w_termsig)
|
|
if (status.w_termsig == WSTOPPED)
|
|
temp = sys_siglist[status.w_stopsig];
|
|
else
|
|
temp = sys_siglist[status.w_termsig];
|
|
}
|
|
|
|
if (first)
|
|
first_job_cond = status;
|
|
else
|
|
if (format)
|
|
{
|
|
if (status.w_status == first_job_cond.w_status)
|
|
temp = "";
|
|
}
|
|
else
|
|
temp = (char *)NULL;
|
|
|
|
if (temp)
|
|
{
|
|
fprintf (stream, "%s", temp);
|
|
|
|
if (strlen (temp))
|
|
name_padding = LONGEST_SIGNAL_DESC - strlen (temp);
|
|
else
|
|
name_padding = LONGEST_SIGNAL_DESC - 2; /* strlen ("| ") */
|
|
|
|
fprintf (stream, "%*s", name_padding, "");
|
|
|
|
if ((status.w_termsig != WSTOPPED) && (status.w_coredump))
|
|
fprintf (stream, "(core dumped) ");
|
|
}
|
|
}
|
|
|
|
if (first)
|
|
{
|
|
/* fprintf (stream, " "); */
|
|
}
|
|
else
|
|
{
|
|
if (format)
|
|
fprintf (stream, "| ");
|
|
}
|
|
|
|
fprintf (stream, "%s", p->command);
|
|
|
|
if (p->next == jobs[index]->pipe)
|
|
{
|
|
if (JOBSTATE (index) == JRUNNING && jobs[index]->foreground == 0)
|
|
fprintf (stream, " &");
|
|
|
|
if (strcmp (get_string_value ("PWD"), jobs[index]->wd) != 0)
|
|
fprintf (stream,
|
|
" (wd: %s)", polite_directory_format (jobs[index]->wd));
|
|
}
|
|
|
|
if (format || (p->next == jobs[index]->pipe))
|
|
fprintf (stream, "\r\n");
|
|
|
|
first = 0;
|
|
p = p->next;
|
|
} while (p != jobs[index]->pipe);
|
|
fflush (stream);
|
|
sigsetmask (oldmask);
|
|
}
|
|
|
|
list_one_job (job, format, ignore, index)
|
|
JOB *job;
|
|
int format, ignore, index;
|
|
{
|
|
pretty_print_job (index, format, stdout);
|
|
return (0);
|
|
}
|
|
|
|
/* List jobs. If FORMAT is non-zero, then the long form of the information
|
|
is printed, else just a short version. */
|
|
list_jobs (format)
|
|
int format;
|
|
{
|
|
cleanup_dead_jobs ();
|
|
map_over_jobs (list_one_job, format, (int) IGNORE_ARG);
|
|
}
|
|
|
|
/* Fork, handling errors. Returns the pid of the newly made child, or 0.
|
|
COMMAND is just for remembering the name of the command; we don't do
|
|
anything else with it. ASYNC_P says what to do with the tty. If
|
|
non-zero, then don't give it away. */
|
|
int
|
|
make_child (command, async_p)
|
|
char *command;
|
|
int async_p;
|
|
{
|
|
int pid, oldmask;
|
|
|
|
oldmask = sigblock (sigmask (SIGINT) | sigmask (SIGCHLD));
|
|
|
|
making_children ();
|
|
|
|
/* Make new environment array if neccessary. */
|
|
maybe_make_export_env ();
|
|
|
|
/* Create the child, handle severe errors. */
|
|
if ((pid = fork ()) < 0)
|
|
{
|
|
extern sighandler throw_to_top_level ();
|
|
|
|
sigsetmask (oldmask);
|
|
report_error ("Memory exhausted or process overflow!");
|
|
throw_to_top_level ();
|
|
}
|
|
|
|
if (!pid)
|
|
{
|
|
/* In the child. Give this child the right process group, set the
|
|
signals to the default state for a new process. */
|
|
signal (SIGINT, SIG_DFL);
|
|
signal (SIGQUIT, SIG_DFL);
|
|
signal (SIGTERM, SIG_DFL);
|
|
|
|
/* Set the resource limits for this child. (In ulimit.c). */
|
|
set_process_resource_limits ();
|
|
|
|
/* Restore the sigmask before changing the tty pgrp, since a
|
|
SIGINT may have occurred in fork (), and we don't want to
|
|
surprise read (). */
|
|
/* sigsetmask (oldmask); */
|
|
|
|
if (job_control)
|
|
{
|
|
/* All processes in this pipeline belong in the same
|
|
process group. */
|
|
|
|
if (!pipeline_pgrp) /* Then this is the first child. */
|
|
pipeline_pgrp = getpid ();
|
|
|
|
/* Check for running command in backquotes. */
|
|
if (pipeline_pgrp == shell_pgrp)
|
|
{
|
|
signal (SIGTSTP, SIG_IGN);
|
|
signal (SIGTTOU, SIG_IGN);
|
|
signal (SIGTTIN, SIG_IGN);
|
|
}
|
|
else
|
|
{
|
|
signal (SIGTSTP, SIG_DFL);
|
|
signal (SIGTTOU, SIG_DFL);
|
|
signal (SIGTTIN, SIG_DFL);
|
|
}
|
|
|
|
if (!async_p)
|
|
give_terminal_to (pipeline_pgrp);
|
|
|
|
setpgrp (0, pipeline_pgrp);
|
|
}
|
|
else /* Without job control... */
|
|
{
|
|
if (!pipeline_pgrp)
|
|
pipeline_pgrp = shell_pgrp;
|
|
|
|
signal (SIGTSTP, SIG_IGN);
|
|
signal (SIGTTOU, SIG_IGN);
|
|
signal (SIGTTIN, SIG_IGN);
|
|
|
|
if (async_p)
|
|
{
|
|
signal (SIGINT, SIG_IGN);
|
|
signal (SIGQUIT, SIG_IGN);
|
|
}
|
|
}
|
|
|
|
if (async_p)
|
|
last_asynchronous_pid = getpid ();
|
|
}
|
|
else
|
|
{
|
|
/* In the parent. Remember the pid of the child just created
|
|
as the proper pgrp if this is the first child. */
|
|
|
|
if (job_control)
|
|
{
|
|
if (!pipeline_pgrp)
|
|
{
|
|
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); */
|
|
}
|
|
setpgid (pid, pipeline_pgrp);
|
|
}
|
|
else
|
|
{
|
|
if (!pipeline_pgrp)
|
|
pipeline_pgrp = shell_pgrp;
|
|
}
|
|
|
|
/* Place all processes into the jobs array regardless of the
|
|
state of job_control. */
|
|
add_process (command, pid);
|
|
|
|
if (async_p)
|
|
last_asynchronous_pid = pid;
|
|
|
|
last_made_pid = pid;
|
|
}
|
|
sigsetmask (oldmask);
|
|
return (pid);
|
|
}
|
|
|
|
/* When we end a job abnormally, or if we stop a job, we set the tty to the
|
|
state kept in here. When a job ends normally, we set the state in here
|
|
to the state of the tty. */
|
|
|
|
#ifdef NEW_TTY_DRIVER
|
|
static struct sgttyb shell_tty_info;
|
|
static struct tchars shell_tchars;
|
|
static struct ltchars shell_ltchars;
|
|
#else
|
|
static struct termio shell_tty_info;
|
|
#endif
|
|
|
|
/* Fill the contents of shell_tty_info with the current tty info. */
|
|
get_tty_state ()
|
|
{
|
|
int tty = open ("/dev/tty", O_RDONLY);
|
|
if (tty != -1)
|
|
{
|
|
#ifdef NEW_TTY_DRIVER
|
|
ioctl (tty, TIOCGETP, &shell_tty_info);
|
|
ioctl (tty, TIOCGETC, &shell_tchars);
|
|
ioctl (tty, TIOCGLTC, &shell_ltchars);
|
|
#else
|
|
ioctl (tty, TCGETA, &shell_tty_info);
|
|
#endif /* NEW_TTY_DRIVER */
|
|
close (tty);
|
|
}
|
|
}
|
|
|
|
/* Make the current tty use the state in shell_tty_info. */
|
|
set_tty_state ()
|
|
{
|
|
int tty = open ("/dev/tty", O_RDONLY);
|
|
if (tty != -1)
|
|
{
|
|
#ifdef NEW_TTY_DRIVER
|
|
ioctl (tty, TIOCSETN, &shell_tty_info);
|
|
ioctl (tty, TIOCSETC, &shell_tchars);
|
|
ioctl (tty, TIOCSLTC, &shell_ltchars);
|
|
#else
|
|
ioctl (tty, TCSETAW, &shell_tty_info);
|
|
#endif /* NEW_TTY_DRIVER */
|
|
close (tty);
|
|
}
|
|
}
|
|
|
|
/* Given an index into the jobs array JOB, return the pid of the last process
|
|
in that job's pipeline. This is the one whose exit status counts. */
|
|
int
|
|
lastproc (job)
|
|
int job;
|
|
{
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
register PROCESS *p;
|
|
|
|
p = jobs[job]->pipe;
|
|
while (p->next != jobs[job]->pipe)
|
|
p = p->next;
|
|
|
|
sigsetmask (oldmask);
|
|
return (p->pid);
|
|
}
|
|
|
|
/* Wait for a particular child of the shell to finish executing.
|
|
This low-level function prints an error message if PID is not
|
|
a child of this shell. It returns -1 if it fails, or 0 if not. */
|
|
int
|
|
wait_for_single_pid (pid)
|
|
int pid;
|
|
{
|
|
register PROCESS *child;
|
|
|
|
child = find_pipeline (pid);
|
|
|
|
if (!child)
|
|
{
|
|
report_error ("wait: pid %d is not a child of this shell", pid);
|
|
return (-1);
|
|
}
|
|
|
|
return (wait_for (pid));
|
|
}
|
|
|
|
/* Wait for all of the backgrounds of this shell to finish. */
|
|
wait_for_background_pids ()
|
|
{
|
|
while (1)
|
|
{
|
|
register int i, count = 0;
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
for (i = 0; i < job_slots; i++)
|
|
if (jobs[i] && (JOBSTATE (i) == JRUNNING) && !(jobs[i]->foreground))
|
|
{
|
|
count++;
|
|
break;
|
|
}
|
|
|
|
if (!count)
|
|
{
|
|
sigsetmask (oldmask);
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < job_slots; i++)
|
|
if (jobs[i] && (JOBSTATE (i) == JRUNNING) && !jobs[i]->foreground)
|
|
{
|
|
int pid = jobs[i]->pgrp;
|
|
sigsetmask (oldmask);
|
|
QUIT;
|
|
wait_for_single_pid (pid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Wait for pid (one of our children) to terminate. */
|
|
int
|
|
wait_for (pid)
|
|
int pid;
|
|
{
|
|
int oldmask, job, termination_state;
|
|
register PROCESS *child;
|
|
extern char *sys_siglist[];
|
|
extern int interactive;
|
|
|
|
oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
/* If we say wait_for (), then we have a record of this child somewhere.
|
|
If this child and all of its peers are not running, then don't
|
|
sigpause (), since there is no need to. */
|
|
wait_loop:
|
|
|
|
/* If the shell is running interactively, then let the user C-c out. */
|
|
if (interactive)
|
|
QUIT;
|
|
|
|
child = find_pipeline (pid);
|
|
|
|
if (!child)
|
|
{
|
|
give_terminal_to (shell_pgrp);
|
|
programming_error ("wait_for: No record of pid %d", pid);
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
job = find_job (pid);
|
|
|
|
if (job != NO_JOB)
|
|
{
|
|
register int job_state = 0, any_stopped = 0;
|
|
register PROCESS *p = jobs[job]->pipe;
|
|
|
|
do
|
|
{
|
|
job_state |= p->running;
|
|
if (!p->running)
|
|
any_stopped |= WIFSTOPPED (p->status);
|
|
p = p->next;
|
|
}
|
|
while (p != jobs[job]->pipe);
|
|
|
|
if (job_state == 0)
|
|
{
|
|
if (any_stopped)
|
|
jobs[job]->state = JSTOPPED;
|
|
else
|
|
jobs[job]->state = JDEAD;
|
|
}
|
|
}
|
|
|
|
if (child->running ||
|
|
((job != NO_JOB) && (JOBSTATE (job) == JRUNNING)))
|
|
{
|
|
sigpause ((long)0);
|
|
goto wait_loop;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
register PROCESS *p = jobs[job]->pipe;
|
|
|
|
while (p->next != jobs[job]->pipe)
|
|
p = p->next;
|
|
termination_state = p->status.w_retcode;
|
|
}
|
|
else
|
|
termination_state = child->status.w_retcode;
|
|
|
|
if (job == NO_JOB || jobs[job]->job_control)
|
|
give_terminal_to (shell_pgrp);
|
|
|
|
/* If the command did not exit cleanly, or the job is just
|
|
being stopped, then reset the tty state back to what it
|
|
was before this command. */
|
|
if ((child->status.w_termsig != 0 || (WIFSTOPPED (child->status))))
|
|
set_tty_state ();
|
|
else
|
|
get_tty_state ();
|
|
|
|
if (job != NO_JOB)
|
|
notify_and_cleanup ();
|
|
|
|
wait_exit:
|
|
sigsetmask (oldmask);
|
|
return (termination_state);
|
|
}
|
|
|
|
/* Wait for the last process in the pipeline for JOB. */
|
|
int
|
|
wait_for_job (job)
|
|
int job;
|
|
{
|
|
int pid = lastproc (job);
|
|
return (wait_for (pid));
|
|
}
|
|
|
|
/* Print info about dead jobs, and then delete them from the list
|
|
of known jobs. */
|
|
notify_and_cleanup ()
|
|
{
|
|
notify_of_job_status ();
|
|
cleanup_dead_jobs ();
|
|
}
|
|
|
|
/* Return the next closest (chronologically) job to JOB which is in
|
|
STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if
|
|
there is no next recent job. */
|
|
static int
|
|
most_recent_job_in_state (job, state)
|
|
int job;
|
|
JOB_STATE state;
|
|
{
|
|
register int i;
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
|
|
for (i = job - 1; i >= 0; i--)
|
|
{
|
|
if (jobs[i])
|
|
{
|
|
if (JOBSTATE (i) == state)
|
|
{
|
|
/* Found it! */
|
|
sigsetmask (oldmask);
|
|
return (i);
|
|
}
|
|
}
|
|
}
|
|
sigsetmask (oldmask);
|
|
return (NO_JOB);
|
|
}
|
|
|
|
/* Return the newest *stopped* job older than JOB, or NO_JOB if not
|
|
found. */
|
|
static int
|
|
last_stopped_job (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)
|
|
int job;
|
|
{
|
|
return (most_recent_job_in_state (job, JRUNNING));
|
|
}
|
|
|
|
/* Make JOB be the current job, and make previous be useful. */
|
|
set_current_job (job)
|
|
int job;
|
|
{
|
|
int candidate = NO_JOB;
|
|
|
|
if (current_job != job)
|
|
{
|
|
previous_job = current_job;
|
|
current_job = job;
|
|
}
|
|
|
|
/* First choice for previous_job is the old current_job. */
|
|
if (previous_job != current_job &&
|
|
previous_job != NO_JOB &&
|
|
JOBSTATE (previous_job) == JSTOPPED)
|
|
return;
|
|
|
|
/* Second choice: Newest stopped job that is older than
|
|
the current job. */
|
|
if (JOBSTATE (current_job) == JSTOPPED)
|
|
{
|
|
candidate = last_stopped_job (current_job);
|
|
|
|
if (candidate != NO_JOB)
|
|
{
|
|
previous_job = candidate;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (JOBSTATE (current_job) == JRUNNING)
|
|
candidate = last_running_job (current_job);
|
|
else
|
|
candidate = last_running_job (job_slots);
|
|
|
|
if (candidate != NO_JOB)
|
|
{
|
|
previous_job = candidate;
|
|
return;
|
|
}
|
|
|
|
/* There is only a single job, and it is both `+' and `-'. */
|
|
previous_job = current_job;
|
|
}
|
|
|
|
/* Make current_job be something useful, if it isn't already. */
|
|
reset_current ()
|
|
{
|
|
int candidate = NO_JOB;
|
|
|
|
if (current_job != NO_JOB &&
|
|
job_slots && jobs[current_job] &&
|
|
JOBSTATE (current_job) == JSTOPPED)
|
|
{
|
|
candidate = current_job;
|
|
}
|
|
else
|
|
{
|
|
/* First choice: the previous job! */
|
|
if (previous_job != NO_JOB && jobs[previous_job] &&
|
|
JOBSTATE (previous_job) == JSTOPPED)
|
|
candidate = previous_job;
|
|
|
|
/* Second choice: the most recently stopped job. */
|
|
candidate = last_stopped_job (job_slots);
|
|
|
|
if (candidate == NO_JOB)
|
|
{
|
|
/* Third choice: the newest running job. */
|
|
candidate = last_running_job (job_slots);
|
|
}
|
|
}
|
|
|
|
/* If we found a job to use, then use it. Otherwise, there
|
|
are no jobs period. */
|
|
if (candidate != NO_JOB)
|
|
set_current_job (candidate);
|
|
else
|
|
current_job = previous_job = NO_JOB;
|
|
}
|
|
|
|
/* Start a job. FOREGROUND if non-zero says to do that. Otherwise,
|
|
start the job in the background. JOB is a zero-based index into
|
|
JOBS. Returns zero if it is unable to start a job. */
|
|
int
|
|
start_job (job, foreground)
|
|
int job, foreground;
|
|
{
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
int already_running = (JOBSTATE (job) == JRUNNING);
|
|
register PROCESS *p;
|
|
|
|
if (!foreground && already_running)
|
|
{
|
|
extern char *this_command_name;
|
|
|
|
report_error ("%s: bg background job?", this_command_name);
|
|
return (0);
|
|
}
|
|
|
|
/* You don't know about the state of this job. Do you? */
|
|
jobs[job]->notified = 0;
|
|
|
|
if (foreground)
|
|
{
|
|
set_current_job (job);
|
|
jobs[job]->foreground = 1;
|
|
}
|
|
|
|
/* Tell the outside world what we're doing. */
|
|
p = jobs[job]->pipe;
|
|
|
|
do
|
|
{
|
|
fprintf (stderr, "%s%s",
|
|
p->command, p->next != jobs[job]->pipe? " | " : "");
|
|
p = p->next;
|
|
}
|
|
while (p != jobs[job]->pipe);
|
|
|
|
if (!foreground)
|
|
fprintf (stderr, " &");
|
|
|
|
if (strcmp (get_string_value ("PWD"), jobs[job]->wd) != 0)
|
|
fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd));
|
|
|
|
fprintf (stderr, "\n");
|
|
|
|
/* Run the job. */
|
|
|
|
if (!already_running)
|
|
{
|
|
/* Each member of the pipeline is now running. */
|
|
p = jobs[job]->pipe;
|
|
|
|
do
|
|
{
|
|
if (WIFSTOPPED (p->status))
|
|
p->running = 1;
|
|
p = p->next;
|
|
}
|
|
while (p != jobs[job]->pipe);
|
|
|
|
/* This means that the job is running. */
|
|
JOBSTATE (job) = JRUNNING;
|
|
}
|
|
|
|
/* Give the terminal to this job. */
|
|
if (foreground)
|
|
{
|
|
if (jobs[job]->job_control)
|
|
give_terminal_to (jobs[job]->pgrp);
|
|
}
|
|
else
|
|
jobs[job]->foreground = 0;
|
|
|
|
/* If the job is already running, then don't bother jump-starting it. */
|
|
if (!already_running)
|
|
{
|
|
jobs[job]->notified = 1;
|
|
killpg (jobs[job]->pgrp, SIGCONT);
|
|
}
|
|
|
|
sigsetmask (oldmask);
|
|
|
|
if (foreground)
|
|
{
|
|
int pid = lastproc (job);
|
|
|
|
return (!wait_for (pid));
|
|
}
|
|
else
|
|
reset_current ();
|
|
|
|
return (1);
|
|
}
|
|
|
|
/* Give PID SIGNAL. This determines what job the pid belongs to (if any).
|
|
If PID does belong to a job, and the job is stopped, then CONTinue the
|
|
job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null,
|
|
then kill the process group associated with PID. */
|
|
int
|
|
kill_pid (pid, signal, group)
|
|
int pid, signal, group;
|
|
{
|
|
int old_mask = sigblock (sigmask (SIGCHLD));
|
|
register PROCESS *p = find_pipeline (pid);
|
|
int job = find_job (pid);
|
|
int result = EXECUTION_SUCCESS;
|
|
|
|
if (group)
|
|
{
|
|
if (job != NO_JOB)
|
|
{
|
|
jobs[job]->notified = 0;
|
|
|
|
/* Kill process in backquotes or one started with job control? */
|
|
if (jobs[job]->pgrp == shell_pgrp)
|
|
{
|
|
p = jobs[job]->pipe;
|
|
|
|
do
|
|
{
|
|
if (!p->running && (signal == SIGTERM || signal == SIGHUP))
|
|
kill (pid, SIGCONT);
|
|
kill (pid, signal);
|
|
p = p->next;
|
|
} while (p != jobs[job]->pipe);
|
|
}
|
|
else
|
|
{
|
|
if (p && (JOBSTATE (job) == JSTOPPED) &&
|
|
(signal == SIGTERM || signal == SIGHUP))
|
|
killpg (jobs[job]->pgrp, SIGCONT);
|
|
result = killpg (jobs[job]->pgrp, signal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = killpg (pid, signal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = kill (pid, signal);
|
|
}
|
|
sigsetmask (old_mask);
|
|
return (result);
|
|
}
|
|
|
|
/* Flush_child () flushes at least one of the children that we are waiting for.
|
|
It gets run when we have gotten a SIGCHLD signal, and stops when there
|
|
aren't any children terminating any more. */
|
|
sighandler
|
|
flush_child (sig, code)
|
|
int sig, code;
|
|
{
|
|
union wait status;
|
|
#ifndef hpux
|
|
struct rusage rusage;
|
|
#endif
|
|
PROCESS *child;
|
|
int pid, call_set_current = 0, last_stopped_job = NO_JOB;
|
|
|
|
do
|
|
{
|
|
#ifdef hpux
|
|
pid = wait3 (&status, (WNOHANG | WUNTRACED), (int *)0);
|
|
#else
|
|
pid = wait3 (&status, (WNOHANG | WUNTRACED), &rusage);
|
|
#endif /* hpux */
|
|
|
|
if (pid > 0)
|
|
{
|
|
#ifdef hpux
|
|
/* Reinstall the signal handler. That's what HPUX makes us do. */
|
|
signal (SIGCHLD, flush_child);
|
|
#endif
|
|
|
|
#ifdef NEVER /* Claim is made that times_builtin can handle it. */
|
|
/* Keep track of total time used. */
|
|
if (! WIFSTOPPED (status))
|
|
add_times (&rusage);
|
|
#endif /* NEVER */
|
|
|
|
/* Locate our PROCESS for this pid. */
|
|
child = find_pipeline (pid);
|
|
|
|
/* It is not an error to have a child terminate that we did
|
|
not have a record of. This child could have been part of
|
|
a pipeline in backquote substitution. */
|
|
if (child)
|
|
{
|
|
int job = find_job (pid);
|
|
|
|
while (child->pid != pid)
|
|
child = child->next;
|
|
|
|
/* Remember status, and fact that process is not running. */
|
|
child->status = status;
|
|
child->running = 0;
|
|
|
|
if (job != NO_JOB)
|
|
{
|
|
int job_state = 0;
|
|
int any_stopped = 0;
|
|
|
|
child = jobs[job]->pipe;
|
|
jobs[job]->notified = 0;
|
|
|
|
/* If all children are not running, but any of them is
|
|
stopped, then the job is stopped, not dead. */
|
|
do
|
|
{
|
|
job_state |= child->running;
|
|
if (!child->running)
|
|
any_stopped |= (WIFSTOPPED (child->status));
|
|
child = child->next;
|
|
}
|
|
while (child != jobs[job]->pipe);
|
|
|
|
if (job_state == 0)
|
|
{
|
|
if (any_stopped)
|
|
{
|
|
jobs[job]->state = JSTOPPED;
|
|
jobs[job]->foreground = 0;
|
|
call_set_current++;
|
|
last_stopped_job = job;
|
|
}
|
|
else
|
|
{
|
|
jobs[job]->state = JDEAD;
|
|
|
|
if (job == last_stopped_job)
|
|
last_stopped_job = NO_JOB;
|
|
|
|
/* If this job was not started with job control,
|
|
then the shell has already seen the SIGINT, since
|
|
the process groups are the same. In that case,
|
|
don't send the SIGINT to the shell; it will
|
|
surprise people to have a stray interrupt
|
|
arriving some time after they killed the job. */
|
|
|
|
if (jobs[job]->foreground &&
|
|
jobs[job]->job_control &&
|
|
jobs[job]->pipe->status.w_termsig == SIGINT)
|
|
kill (getpid (), SIGINT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (pid > 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 ();
|
|
|
|
/* 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)
|
|
notify_of_job_status ();
|
|
}
|
|
|
|
/* Function to call when you want to notify people of changes
|
|
in job status. This prints out all jobs which are pending
|
|
notification to stderr, and marks those printed as already
|
|
notified, thus making them candidates for cleanup. */
|
|
notify_of_job_status ()
|
|
{
|
|
extern char *sys_siglist[];
|
|
register int job, termsig;
|
|
char *dir = (char *)get_string_value ("PWD");
|
|
int oldmask = sigblock (sigmask (SIGCHLD) | sigmask (SIGTTOU));
|
|
|
|
for (job = 0; job < job_slots; job++)
|
|
{
|
|
if (jobs[job] && jobs[job]->notified == 0)
|
|
{
|
|
termsig = jobs[job]->pipe->status.w_termsig;
|
|
|
|
switch (JOBSTATE (job))
|
|
{
|
|
/* Print info on jobs that are running in the background,
|
|
and on foreground jobs that were killed by anything
|
|
except SIGINT. */
|
|
|
|
case JDEAD:
|
|
|
|
if (jobs[job]->foreground)
|
|
{
|
|
if (termsig && termsig != WSTOPPED && termsig != SIGINT)
|
|
{
|
|
fprintf (stderr, "%s", sys_siglist[termsig]);
|
|
|
|
if (jobs[job]->pipe->status.w_coredump)
|
|
fprintf (stderr, " (core dumped)");
|
|
|
|
fprintf (stderr, "\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pretty_print_job (job, 0, stderr);
|
|
if (dir && strcmp (dir, jobs[job]->wd) != 0)
|
|
fprintf (stderr,
|
|
"(wd now: %s)\n", polite_directory_format (dir));
|
|
}
|
|
jobs[job]->notified = 1;
|
|
break;
|
|
|
|
case JSTOPPED:
|
|
fprintf (stderr, "\n");
|
|
pretty_print_job (job, 0, stderr);
|
|
if (dir && (strcmp (dir, jobs[job]->wd) != 0))
|
|
fprintf (stderr,
|
|
"(wd now: %s)\n", polite_directory_format (dir));
|
|
jobs[job]->notified = 1;
|
|
break;
|
|
|
|
case JRUNNING:
|
|
case JMIXED:
|
|
break;
|
|
|
|
default:
|
|
programming_error ("notify_of_job_status");
|
|
}
|
|
}
|
|
}
|
|
sigsetmask (oldmask);
|
|
}
|
|
|
|
#ifndef hpux
|
|
add_times (rused)
|
|
struct rusage *rused;
|
|
{
|
|
total_systime.tv_usec += rused->ru_stime.tv_usec;
|
|
total_systime.tv_sec += rused->ru_stime.tv_sec;
|
|
|
|
if (total_systime.tv_usec > 1000000)
|
|
{
|
|
total_systime.tv_sec++;
|
|
total_systime.tv_usec -= 1000000;
|
|
}
|
|
|
|
total_usertime.tv_usec += rused->ru_utime.tv_usec;
|
|
total_usertime.tv_sec += rused->ru_utime.tv_sec;
|
|
|
|
if (total_usertime.tv_usec > 1000000)
|
|
{
|
|
total_usertime.tv_sec++;
|
|
total_usertime.tv_usec -= 1000000;
|
|
}
|
|
|
|
if (total_systime.tv_sec)
|
|
{
|
|
system_minutes_used = (total_systime.tv_sec / 60);
|
|
system_seconds_used = (total_systime.tv_sec % 60);
|
|
}
|
|
|
|
if (total_usertime.tv_sec)
|
|
{
|
|
user_minutes_used = (total_usertime.tv_sec / 60);
|
|
user_seconds_used = (total_usertime.tv_sec % 60);
|
|
}
|
|
}
|
|
#endif /* hpux */
|
|
|
|
#ifdef hpux
|
|
getdtablesize ()
|
|
{
|
|
return (NOFILE);
|
|
}
|
|
#endif /* hpux */
|
|
|
|
/* Initialize the job control mechanism, and set up the tty stuff. */
|
|
initialize_jobs ()
|
|
{
|
|
extern int interactive;
|
|
|
|
shell_pgrp = getpgrp (0);
|
|
|
|
/* We can only have job control if we are interactive?
|
|
I guess that makes sense. */
|
|
|
|
if (!job_control || !interactive)
|
|
{
|
|
job_control = 0;
|
|
}
|
|
else
|
|
{
|
|
char *err_string = "get";
|
|
|
|
/* Make sure that we are using the new line discipline. */
|
|
int ldisc;
|
|
|
|
/* Get our controlling terminal. If job_control is set, or
|
|
interactive is set, then this is an interactive shell no
|
|
matter what opening /dev/tty returns. (It sometimes says
|
|
the wrong thing.) */
|
|
shell_tty = open ("/dev/tty", O_RDWR, 0666);
|
|
if (shell_tty < 0)
|
|
shell_tty = dup (fileno (stdin));
|
|
|
|
/* Find the highest unused file descriptor we can. */
|
|
{
|
|
int ignore, nds = getdtablesize ();
|
|
|
|
while (--nds > 3)
|
|
{
|
|
if (fcntl (nds, F_GETFD, &ignore) == -1)
|
|
break;
|
|
}
|
|
|
|
if (shell_tty != nds && (dup2 (shell_tty, nds) != -1))
|
|
{
|
|
if (shell_tty != fileno (stdin))
|
|
close (shell_tty);
|
|
shell_tty = nds;
|
|
}
|
|
}
|
|
|
|
while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1)
|
|
{
|
|
if (shell_pgrp != terminal_pgrp)
|
|
{
|
|
SigHandler *old_ttin = (SigHandler *)signal (SIGTTIN, SIG_DFL);
|
|
kill (0, SIGTTIN);
|
|
signal (SIGTTIN, old_ttin);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef NEW_TTY_DRIVER
|
|
if (ioctl (shell_tty, TIOCGETD, &ldisc) < 0)
|
|
goto bad_ioctl;
|
|
|
|
if (ldisc != NTTYDISC)
|
|
{
|
|
ldisc = NTTYDISC;
|
|
err_string = "set";
|
|
if (ioctl (shell_tty, TIOCSETD, &ldisc) < 0)
|
|
{
|
|
bad_ioctl:
|
|
fprintf (stderr, "initialize_jobs: %s line disc: ", err_string);
|
|
job_control = 0;
|
|
file_error ("jobs.c");
|
|
}
|
|
}
|
|
#endif /* NEW_TTY_DRIVER */
|
|
|
|
original_pgrp = shell_pgrp;
|
|
shell_pgrp = getpid ();
|
|
give_terminal_to (shell_pgrp);
|
|
setpgid (0, shell_pgrp);
|
|
|
|
#ifndef FD_CLOEXEC
|
|
#define FD_CLOEXEC 1
|
|
#endif
|
|
|
|
if (shell_tty != fileno (stdin))
|
|
fcntl (shell_tty, F_SETFD, FD_CLOEXEC);
|
|
|
|
job_control = 1;
|
|
}
|
|
|
|
signal (SIGCHLD, flush_child);
|
|
/* We don't call set_job_control here, because change_flag_char ()
|
|
does that for us. */
|
|
/* set_job_control (job_control); */
|
|
change_flag_char ('m', job_control ? '-' : '+');
|
|
|
|
get_tty_state ();
|
|
}
|
|
|
|
/* Allow or disallow job control to take place. */
|
|
set_job_control (arg)
|
|
int arg;
|
|
{
|
|
job_control = arg;
|
|
}
|
|
|
|
static SigHandler *old_tstp, *old_ttou, *old_ttin;
|
|
static SigHandler *old_cont = (SigHandler *)SIG_DFL;
|
|
|
|
/* Setup this shell to handle C-C, etc. */
|
|
initialize_job_signals ()
|
|
{
|
|
extern int login_shell;
|
|
sighandler sigint_sighandler ();
|
|
|
|
signal (SIGINT, sigint_sighandler);
|
|
signal (SIGQUIT, SIG_IGN);
|
|
|
|
if (login_shell)
|
|
{
|
|
signal (SIGTSTP, SIG_IGN);
|
|
signal (SIGTTOU, SIG_IGN);
|
|
signal (SIGTTIN, SIG_IGN);
|
|
}
|
|
else
|
|
{
|
|
static sighandler stop_signal_handler ();
|
|
|
|
old_tstp = (SigHandler *)signal (SIGTSTP, stop_signal_handler);
|
|
old_ttou = (SigHandler *)signal (SIGTTOU, stop_signal_handler);
|
|
old_ttin = (SigHandler *)signal (SIGTTIN, stop_signal_handler);
|
|
}
|
|
}
|
|
|
|
/* Here we handle CONT signals. */
|
|
static sighandler
|
|
cont_signal_handler (sig, code)
|
|
int sig, code;
|
|
{
|
|
initialize_job_signals ();
|
|
signal (SIGCONT, old_cont);
|
|
kill (getpid (), SIGCONT);
|
|
}
|
|
|
|
/* Here we handle stop signals while we are running not as a login shell. */
|
|
static sighandler
|
|
stop_signal_handler (sig, code)
|
|
int sig, code;
|
|
{
|
|
signal (SIGTSTP, old_tstp);
|
|
signal (SIGTTOU, old_ttou);
|
|
signal (SIGTTIN, old_ttin);
|
|
|
|
old_cont = (SigHandler *)signal (SIGCONT, cont_signal_handler);
|
|
|
|
give_terminal_to (shell_pgrp);
|
|
|
|
kill (getpid (), sig);
|
|
}
|
|
|
|
/* Give the terminal to PGRP. */
|
|
give_terminal_to (pgrp)
|
|
int pgrp;
|
|
{
|
|
int oldmask;
|
|
|
|
if (job_control)
|
|
{
|
|
oldmask = sigblock (sigmask (SIGTTOU) |
|
|
sigmask (SIGTTIN) |
|
|
sigmask (SIGTSTP) |
|
|
sigmask (SIGCHLD));
|
|
|
|
terminal_pgrp = pgrp;
|
|
tcsetpgrp (shell_tty, terminal_pgrp);
|
|
sigsetmask (oldmask);
|
|
}
|
|
}
|
|
|
|
/* Clear out any jobs in the job array. This is intended to be used by
|
|
children of the shell, who should not have any job structures as baggage
|
|
when they start executing (forking subshells for parenthesized execution
|
|
and functions with pipes are the two that spring to mind). */
|
|
|
|
delete_all_jobs ()
|
|
{
|
|
if (job_slots)
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; i < job_slots; i++)
|
|
if (jobs[i] != (JOB *) NULL)
|
|
delete_job (i);
|
|
|
|
free ((char *)jobs);
|
|
job_slots = 0;
|
|
}
|
|
}
|
|
|
|
/* Turn off all traces of job control. This is run by children of the shell
|
|
which are going to do shellsy things, like wait (), etc. */
|
|
without_job_control ()
|
|
{
|
|
stop_making_children ();
|
|
start_pipeline ();
|
|
delete_all_jobs ();
|
|
set_job_control (0);
|
|
}
|
|
#endif /* JOB_CONTROL */
|
|
|