* Copyright (c) 1991 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)jobs.c 5.1 (Berkeley) %G%";
#undef CEOF /* syntax.h redefines this */
#include <sys/resource.h>
struct job
*jobtab
; /* array of jobs */
int njobs
; /* size of array */
MKINIT
short backgndpid
= -1; /* pid of last background process */
int initialpgrp
; /* pgrp of shell on invocation */
short curjob
; /* current job */
STATIC
void restartjob(struct job
*);
STATIC
struct job
*getjob(char *);
STATIC
void freejob(struct job
*);
STATIC
int procrunning(int);
STATIC
int dowait(int, struct job
*);
STATIC
int waitproc(int, int *);
STATIC
char *commandtext(union node
*);
STATIC
void restartjob();
STATIC
struct job
*getjob();
STATIC
int procrunning();
STATIC
char *commandtext();
* Turn job control on and off.
* Note: This code assumes that the third arg to ioctl is a character
* pointer, which is true on Berkeley systems but not System V. Since
* System V doesn't have job control yet, this isn't a problem now.
if (on
== jobctl
|| rootshell
== 0)
do { /* while we are in the background */
if (ioctl(2, TIOCGPGRP
, (char *)&initialpgrp
) < 0) {
out2str("ash: can't access tty; job control turned off\n");
initialpgrp
= getpgrp(0);
else if (initialpgrp
!= getpgrp(0)) {
killpg(initialpgrp
, SIGTTIN
);
if (ioctl(2, TIOCGETD
, (char *)&ldisc
) < 0 || ldisc
!= NTTYDISC
) {
out2str("ash: need new tty driver to run job control; job control turned off\n");
ioctl(2, TIOCSPGRP
, (char *)&rootpid
);
} else { /* turning job control off */
ioctl(2, TIOCSPGRP
, (char *)&initialpgrp
);
fgcmd(argc
, argv
) char **argv
; {
error("job not created under job control");
ioctl(2, TIOCSPGRP
, (char *)&pgrp
);
bgcmd(argc
, argv
) char **argv
; {
error("job not created under job control");
if (jp
->state
== JOBDONE
)
killpg(jp
->ps
[0].pid
, SIGCONT
);
for (ps
= jp
->ps
, i
= jp
->nprocs
; --i
>= 0 ; ps
++) {
if ((ps
->status
& 0377) == 0177) {
jobscmd(argc
, argv
) char **argv
; {
* Print a list of jobs. If "change" is nonzero, only print jobs whose
* statuses have changed since the last call to showjobs.
* If the shell is interrupted in the process of creating a job, the
* result may be a job structure containing zero processes. Such structures
TRACE(("showjobs(%d) called\n", change
));
while (dowait(0, (struct job
*)NULL
) > 0);
for (jobno
= 1, jp
= jobtab
; jobno
<= njobs
; jobno
++, jp
++) {
if (change
&& ! jp
->changed
)
for (ps
= jp
->ps
; ; ps
++) { /* for each process */
fmtstr(s
, 64, "[%d] %d ", jobno
, ps
->pid
);
fmtstr(s
, 64, " %d ", ps
->pid
);
/* don't print anything */
} else if ((ps
->status
& 0xFF) == 0) {
fmtstr(s
, 64, "Exit %d", ps
->status
>> 8);
if ((i
& 0x7F) <= MAXSIG
&& sigmesg
[i
& 0x7F])
scopy(sigmesg
[i
& 0x7F], s
);
fmtstr(s
, 64, "Signal %d", i
& 0x7F);
strcat(s
, " (core dumped)");
if (jp
->state
== JOBDONE
) {
* Mark a job structure as unused.
for (i
= jp
->nprocs
, ps
= jp
->ps
; --i
>= 0 ; ps
++) {
if (curjob
== jp
- jobtab
+ 1)
waitcmd(argc
, argv
) char **argv
; {
for (;;) { /* loop until process terminated or stopped */
status
= job
->ps
[job
->nprocs
- 1].status
;
if ((status
& 0xFF) == 0)
status
= status
>> 8 & 0xFF;
else if ((status
& 0xFF) == 0177)
status
= (status
>> 8 & 0x7F) + 128;
status
= (status
& 0x7F) + 128;
for (jp
= jobtab
; ; jp
++) {
if (jp
>= jobtab
+ njobs
) { /* no running procs */
if (jp
->used
&& jp
->state
== 0)
dowait(1, (struct job
*)NULL
);
jobidcmd(argc
, argv
) char **argv
; {
for (i
= 0 ; i
< jp
->nprocs
; ) {
out1fmt("%d", jp
->ps
[i
].pid
);
out1c(++i
< jp
->nprocs
? ' ' : '\n');
* Convert a job name to a job structure.
if ((jobno
= curjob
) == 0 || jobtab
[jobno
- 1].used
== 0)
return &jobtab
[jobno
- 1];
} else if (name
[0] == '%') {
jobno
= number(name
+ 1);
if (jobno
> 0 && jobno
<= njobs
&& jobtab
[jobno
- 1].used
!= 0)
return &jobtab
[jobno
- 1];
} else if (name
[1] == '%' && name
[2] == '\0') {
register struct job
*found
= NULL
;
for (jp
= jobtab
, i
= njobs
; --i
>= 0 ; jp
++) {
if (jp
->used
&& jp
->nprocs
> 0
&& prefix(name
+ 1, jp
->ps
[0].cmd
)) {
error("%s: ambiguous", name
);
} else if (is_number(name
)) {
for (jp
= jobtab
, i
= njobs
; --i
>= 0 ; jp
++) {
if (jp
->used
&& jp
->nprocs
> 0
&& jp
->ps
[jp
->nprocs
- 1].pid
== pid
)
error("No such job: %s", name
);
* Return a new job structure,
for (i
= njobs
, jp
= jobtab
; ; jp
++) {
jobtab
= ckmalloc(4 * sizeof jobtab
[0]);
jp
= ckmalloc((njobs
+ 4) * sizeof jobtab
[0]);
bcopy(jobtab
, jp
, njobs
* sizeof jp
[0]);
for (i
= 4 ; --i
>= 0 ; jobtab
[njobs
++].used
= 0);
jp
->ps
= ckmalloc(nprocs
* sizeof (struct procstat
));
TRACE(("makejob(0x%x, %d) returns %%%d\n", (int)node
, nprocs
, jp
- jobtab
+ 1));
* Fork of a subshell. If we are doing job control, give the subshell its
* own process group. Jp is a job structure that the job is to be added to.
* N is the command that will be evaluated by the child. Both jp and n may
* be NULL. The mode parameter can be one of the following:
* FORK_FG - Fork off a foreground process.
* FORK_BG - Fork off a background process.
* FORK_NOJOB - Like FORK_FG, but don't give the process its own
* process group even if job control is on.
* When job control is turned off, background processes have their standard
* input redirected to /dev/null (except for the second and later processes
TRACE(("forkshell(%%%d, 0x%x, %d) called\n", jp
- jobtab
, (int)n
, mode
));
TRACE(("Fork failed, errno=%d\n", errno
));
TRACE(("Child shell %d\n", getpid()));
for (i
= njobs
, p
= jobtab
; --i
>= 0 ; p
++)
jobctl
= 0; /* do job control only in root shell */
if (wasroot
&& mode
!= FORK_NOJOB
&& jflag
) {
if (jp
== NULL
|| jp
->nprocs
== 0)
/*** this causes superfluous TIOCSPGRPS ***/
if (ioctl(2, TIOCSPGRP
, (char *)&pgrp
) < 0)
error("TIOCSPGRP failed, errno=%d\n", errno
);
} else if (mode
== FORK_BG
) {
if (jp
== NULL
|| jp
->nprocs
== 0) {
if (open("/dev/null", O_RDONLY
) != 0)
error("Can't open /dev/null");
if (jp
== NULL
|| jp
->nprocs
== 0) {
if (open("/dev/null", O_RDONLY
) != 0)
error("Can't open /dev/null");
if (rootshell
&& mode
!= FORK_NOJOB
&& jflag
) {
if (jp
== NULL
|| jp
->nprocs
== 0)
backgndpid
= pid
; /* set $! */
struct procstat
*ps
= &jp
->ps
[jp
->nprocs
++];
if (iflag
&& rootshell
&& n
)
ps
->cmd
= commandtext(n
);
TRACE(("In parent shell: child = %d\n", pid
));
* Wait for job to finish.
* Under job control we have the problem that while a child process is
* running interrupts generated by the user are sent to the child but not
* to the shell. This means that an infinite loop started by an inter-
* active user may be hard to kill. With job control turned off, an
* interactive user may place an interactive program inside a loop. If
* the interactive program catches interrupts, the user doesn't want
* these interrupts to also abort the loop. The approach we take here
* is to have the shell ignore interrupt signals while waiting for a
* forground process to terminate, and then send itself an interrupt
* signal if the child process was terminated by an interrupt signal.
* Unfortunately, some programs want to do a bit of cleanup and then
* exit on interrupt; unless these processes terminate themselves by
* sending a signal to themselves (instead of calling exit) they will
TRACE(("waitforjob(%%%d) called\n", jp
- jobtab
+ 1));
if (ioctl(2, TIOCSPGRP
, (char *)&mypgrp
) < 0)
error("TIOCSPGRP failed, errno=%d\n", errno
);
if (jp
->state
== JOBSTOPPED
)
curjob
= jp
- jobtab
+ 1;
status
= jp
->ps
[jp
->nprocs
- 1].status
;
if ((status
& 0xFF) == 0)
else if ((status
& 0xFF) == 0177)
st
= (status
>> 8 & 0x7F) + 128;
st
= (status
& 0x7F) + 128;
if (! JOBS
|| jp
->state
== JOBDONE
)
if ((status
& 0x7F) == SIGINT
)
* Wait for a process to terminate.
TRACE(("dowait(%d) called\n", block
));
pid
= waitproc(block
, &status
);
TRACE(("wait returns %d, status=%d\n", pid
, status
));
} while (pid
== -1 && errno
== EINTR
);
for (jp
= jobtab
; jp
< jobtab
+ njobs
; jp
++) {
for (sp
= jp
->ps
; sp
< jp
->ps
+ jp
->nprocs
; sp
++) {
TRACE(("Changin status of proc %d from 0x%x to 0x%x\n", pid
, sp
->status
, status
));
else if ((sp
->status
& 0377) == 0177)
if (stopped
) { /* stopped or done */
int state
= done
? JOBDONE
: JOBSTOPPED
;
if (jp
->state
!= state
) {
TRACE(("Job %d: changing state from %d to %d\n", jp
- jobtab
+ 1, jp
->state
, state
));
if (done
&& curjob
== jp
- jobtab
+ 1)
curjob
= 0; /* no current job */
if (! rootshell
|| ! iflag
|| (job
&& thisjob
== job
)) {
if ((status
& 0xFF) == 0177)
if (status
!= 0 && status
!= SIGINT
&& status
!= SIGPIPE
) {
outfmt(out2
, "%d: ", pid
);
if (status
== SIGTSTP
&& rootshell
&& iflag
)
outfmt(out2
, "%%%d ", job
- jobtab
+ 1);
if (status
<= MAXSIG
&& sigmesg
[status
])
out2str(sigmesg
[status
]);
outfmt(out2
, "Signal %d", status
);
out2str(" - core dumped");
TRACE(("Not printing status: status=%d\n", status
));
TRACE(("Not printing status, rootshell=%d, job=0x%x\n", rootshell
, job
));
* Do a wait system call. If job control is compiled in, we accept
* stopped processes. If block is zero, we return a value of zero
* System V doesn't have a non-blocking wait system call. It does
* have a SIGCLD signal that is sent to a process when one of it's
* children dies. The obvious way to use SIGCLD would be to install
* a handler for SIGCLD which simply bumped a counter when a SIGCLD
* was received, and have waitproc bump another counter when it got
* the status of a process. Waitproc would then know that a wait
* system call would not block if the two counters were different.
* This approach doesn't work because if a process has children that
* have not been waited for, System V will send it a SIGCLD when it
* installs a signal handler for SIGCLD. What this means is that when
* a child exits, the shell will be sent SIGCLD signals continuously
* until is runs out of stack space, unless it does a wait call before
* restoring the signal handler. The code below takes advantage of
* this (mis)feature by installing a signal handler for SIGCLD and
* then checking to see whether it was called. If there are any
* children to be waited for, it will be.
* If neither SYSV nor BSD is defined, we don't implement nonblocking
* waits at all. In this case, the user will not be informed when
* a background process until the next time she runs a real program
* (as opposed to running a builtin command or just typing return),
* and the jobs command may give out of date information.
STATIC
int onsigchild() {
return wait3((union wait
*)status
, flags
, (struct rusage
*)NULL
);
save
= signal(SIGCLD
, onsigchild
);
* Return a string identifying a command (to be printed by the
STATIC
void cmdtxt(), cmdputs();
cmdnextc
= name
= ckmalloc(50);
for (lp
= n
->npipe
.cmdlist
; lp
; lp
= lp
->next
) {
cmdputs(n
->ncase
.expr
->narg
.text
);
for (np
= n
->ncmd
.args
; np
; np
= np
->narg
.next
) {
for (np
= n
->ncmd
.redirect
; np
; np
= np
->nfile
.next
) {
p
= ">"; i
= 1; goto redir
;
p
= ">>"; i
= 1; goto redir
;
p
= ">&"; i
= 1; goto redir
;
p
= "<"; i
= 0; goto redir
;
p
= "<&"; i
= 0; goto redir
;
s
[0] = n
->nfile
.fd
+ '0';
if (n
->type
== NTOFD
|| n
->type
== NFROMFD
) {
s
[0] = n
->ndup
.dupfd
+ '0';
while ((c
= *p
++) != '\0') {
} else if (c
== '=' && subtype
!= 0) {
*q
++ = "}-+?="[(subtype
& VSTYPE
) - VSNORMAL
];
} else if (c
== CTLENDVAR
) {
} else if (c
== CTLBACKQ
| c
== CTLBACKQ
+CTLQUOTE
)
cmdnleft
++; /* ignore it */