Add '-n' and '-p var' args to the wait command (-n: wait for any,
-p var: set var to identifier, from arg list, or PID if no job args) of the job for which status is returned (becomes $? after wait.) Note: var is unset if the status returned from wait came from wait itself rather than from some job exiting (so it is now possible to tell whether 127 means "no such job" or "job did exit(127)", and whether $? > 128 means "wait was interrupted" or "job was killed by a signal or did exit(>128)". ($? is too limited to to allow indicating whether the job died with a signal, or exited with a status such that it looks like it did...)
This commit is contained in:
parent
792031649a
commit
4e9fc30dca
272
bin/sh/jobs.c
272
bin/sh/jobs.c
|
@ -1,4 +1,4 @@
|
|||
/* $NetBSD: jobs.c,v 1.94 2017/10/28 04:50:38 kre Exp $ */
|
||||
/* $NetBSD: jobs.c,v 1.95 2017/10/28 06:36:17 kre Exp $ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 1991, 1993
|
||||
|
@ -37,10 +37,11 @@
|
|||
#if 0
|
||||
static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95";
|
||||
#else
|
||||
__RCSID("$NetBSD: jobs.c,v 1.94 2017/10/28 04:50:38 kre Exp $");
|
||||
__RCSID("$NetBSD: jobs.c,v 1.95 2017/10/28 06:36:17 kre Exp $");
|
||||
#endif
|
||||
#endif /* not lint */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
|
@ -71,6 +72,7 @@ __RCSID("$NetBSD: jobs.c,v 1.94 2017/10/28 04:50:38 kre Exp $");
|
|||
#include "parser.h"
|
||||
#include "nodes.h"
|
||||
#include "jobs.h"
|
||||
#include "var.h"
|
||||
#include "options.h"
|
||||
#include "builtins.h"
|
||||
#include "trap.h"
|
||||
|
@ -103,7 +105,7 @@ static int ttyfd = -1;
|
|||
STATIC void restartjob(struct job *);
|
||||
STATIC void freejob(struct job *);
|
||||
STATIC struct job *getjob(const char *, int);
|
||||
STATIC int dowait(int, struct job *);
|
||||
STATIC int dowait(int, struct job *, struct job **);
|
||||
#define WBLOCK 1
|
||||
#define WNOFREE 2
|
||||
#define WSILENT 4
|
||||
|
@ -519,12 +521,11 @@ showjob(struct output *out, struct job *jp, int mode)
|
|||
outc('\n', out);
|
||||
}
|
||||
flushout(out);
|
||||
jp->changed = 0;
|
||||
jp->flags &= ~JOBCHANGED;
|
||||
if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE))
|
||||
freejob(jp);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
jobscmd(int argc, char **argv)
|
||||
{
|
||||
|
@ -567,8 +568,8 @@ showjobs(struct output *out, int mode)
|
|||
CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode));
|
||||
|
||||
/* If not even one one job changed, there is nothing to do */
|
||||
gotpid = dowait(WSILENT, NULL);
|
||||
while (dowait(WSILENT, NULL) > 0)
|
||||
gotpid = dowait(WSILENT, NULL, NULL);
|
||||
while (dowait(WSILENT, NULL, NULL) > 0)
|
||||
continue;
|
||||
#ifdef JOBS
|
||||
/*
|
||||
|
@ -593,10 +594,10 @@ showjobs(struct output *out, int mode)
|
|||
freejob(jp);
|
||||
continue;
|
||||
}
|
||||
if ((mode & SHOW_CHANGED) && !jp->changed)
|
||||
if ((mode & SHOW_CHANGED) && !(jp->flags & JOBCHANGED))
|
||||
continue;
|
||||
if (silent && jp->changed) {
|
||||
jp->changed = 0;
|
||||
if (silent && (jp->flags & JOBCHANGED)) {
|
||||
jp->flags &= ~JOBCHANGED;
|
||||
continue;
|
||||
}
|
||||
showjob(out, jp, mode);
|
||||
|
@ -663,11 +664,35 @@ jobstatus(const struct job *jp, int raw)
|
|||
int
|
||||
waitcmd(int argc, char **argv)
|
||||
{
|
||||
struct job *job;
|
||||
struct job *job, *last;
|
||||
int retval;
|
||||
struct job *jp;
|
||||
int i;
|
||||
int any = 0;
|
||||
int found;
|
||||
char *pid = NULL, *fpid;
|
||||
char **arg;
|
||||
char idstring[20];
|
||||
|
||||
nextopt("");
|
||||
while ((i = nextopt("np:")) != '\0') {
|
||||
switch (i) {
|
||||
case 'n':
|
||||
any = 1;
|
||||
break;
|
||||
case 'p':
|
||||
if (pid)
|
||||
error("more than one -p unsupported");
|
||||
pid = optionarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pid != NULL) {
|
||||
if (!validname(pid, '\0', NULL))
|
||||
error("invalid name: -p '%s'", pid);
|
||||
if (unsetvar(pid, 0))
|
||||
error("%s readonly", pid);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have forked, and not yet created any new jobs, then
|
||||
|
@ -675,48 +700,178 @@ waitcmd(int argc, char **argv)
|
|||
* so simply return in that case.
|
||||
*
|
||||
* The return code is 127 if we had any pid args (none are found)
|
||||
* but 0 for plain old "wait".
|
||||
* or if we had -n (nothing exited), but 0 for plain old "wait".
|
||||
*/
|
||||
if (jobs_invalid)
|
||||
return *argptr ? 127 : 0;
|
||||
|
||||
if (!*argptr) {
|
||||
/* wait for all jobs */
|
||||
jp = jobtab;
|
||||
for (;;) {
|
||||
if (jp >= jobtab + njobs) {
|
||||
/* no running procs */
|
||||
return 0;
|
||||
}
|
||||
if (!jp->used || jp->state != JOBRUNNING) {
|
||||
jp++;
|
||||
continue;
|
||||
}
|
||||
if (dowait(WBLOCK, NULL) == -1)
|
||||
return 128 + lastsig();
|
||||
jp = jobtab;
|
||||
}
|
||||
if (jobs_invalid) {
|
||||
CTRACE(DBG_WAIT, ("builtin wait%s%s in child, invalid jobtab\n",
|
||||
any ? " -n" : "", *argptr ? " pid..." : ""));
|
||||
return (any || *argptr) ? 127 : 0;
|
||||
}
|
||||
|
||||
retval = 127; /* XXXGCC: -Wuninitialized */
|
||||
for (; *argptr; argptr++) {
|
||||
job = getjob(*argptr, 1);
|
||||
if (!job) {
|
||||
retval = 127;
|
||||
/* clear stray flags left from previous waitcmd */
|
||||
for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
|
||||
jp->flags &= ~JOBWANTED;
|
||||
jp->ref = NULL;
|
||||
}
|
||||
|
||||
CTRACE(DBG_WAIT,
|
||||
("builtin wait%s%s\n", any ? " -n" : "", *argptr ? " pid..." : ""));
|
||||
|
||||
/*
|
||||
* First, validate the jobnum args, count how many refer to
|
||||
* (different) running jobs, and if we had -n, and found that one has
|
||||
* already finished, we return that one. Otherwise remember
|
||||
* which ones we are looking for (JOBWANTED).
|
||||
*/
|
||||
found = 0;
|
||||
last = NULL;
|
||||
for (arg = argptr; *arg; arg++) {
|
||||
last = jp = getjob(*arg, 1);
|
||||
if (!jp)
|
||||
continue;
|
||||
if (jp->ref == NULL)
|
||||
jp->ref = *arg;
|
||||
if (any && jp->state == JOBDONE) {
|
||||
/*
|
||||
* We just want any of them, and this one is
|
||||
* ready for consumption, bon apetit ...
|
||||
*/
|
||||
retval = jobstatus(jp, 0);
|
||||
if (pid)
|
||||
setvar(pid, *arg, 0);
|
||||
if (!iflag)
|
||||
freejob(jp);
|
||||
CTRACE(DBG_WAIT, ("wait -n found %s already done: %d\n", *arg, retval));
|
||||
return retval;
|
||||
}
|
||||
if (!(jp->flags & JOBWANTED)) {
|
||||
/*
|
||||
* It is possible to list the same job several
|
||||
* times - the obvious "wait 1 1 1" or
|
||||
* "wait %% %2 102" where job 2 is current and pid 102
|
||||
* However many times it is requested, it is found once.
|
||||
*/
|
||||
found++;
|
||||
jp->flags |= JOBWANTED;
|
||||
}
|
||||
job = jp;
|
||||
}
|
||||
|
||||
VTRACE(DBG_WAIT, ("wait %s%s%sfound %d candidates (last %s)\n",
|
||||
any ? "-n " : "", *argptr ? *argptr : "",
|
||||
argptr[0] && argptr[1] ? "... " : " ", found,
|
||||
job ? (job->ref ? job->ref : "<no-arg>") : "none"));
|
||||
|
||||
/*
|
||||
* If we were given a list of jobnums:
|
||||
* and none of those exist, then we're done.
|
||||
*/
|
||||
if (*argptr && found == 0)
|
||||
return 127;
|
||||
|
||||
/*
|
||||
* Otherwise we need to wait for something to complete
|
||||
* When it does, we check and see if it is one of the
|
||||
* jobs we're waiting on, and if so, we clean it up.
|
||||
* If we had -n, then we're done, otherwise we do it all again
|
||||
* until all we had listed are done, of if there were no
|
||||
* jobnum args, all are done.
|
||||
*/
|
||||
|
||||
retval = any || *argptr ? 127 : 0;
|
||||
fpid = NULL;
|
||||
for (;;) {
|
||||
VTRACE(DBG_WAIT, ("wait waiting (%d remain): ", found));
|
||||
for (jp = jobtab, i = njobs; --i >= 0; jp++) {
|
||||
if (jp->used && jp->flags & JOBWANTED &&
|
||||
jp->state == JOBDONE)
|
||||
break;
|
||||
if (jp->used && jp->state == JOBRUNNING)
|
||||
break;
|
||||
}
|
||||
if (i < 0) {
|
||||
CTRACE(DBG_WAIT, ("nothing running (ret: %d) fpid %s\n",
|
||||
retval, fpid ? fpid : "unset"));
|
||||
if (pid && fpid)
|
||||
setvar(pid, fpid, 0);
|
||||
return retval;
|
||||
}
|
||||
VTRACE(DBG_WAIT, ("found @%d/%d state: %d\n", njobs-i, njobs,
|
||||
jp->state));
|
||||
|
||||
/*
|
||||
* There is at least 1 job running, so we can
|
||||
* safely wait() for something to exit.
|
||||
*/
|
||||
if (jp->state == JOBRUNNING) {
|
||||
job = NULL;
|
||||
if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1)
|
||||
return 128 + lastsig();
|
||||
|
||||
/*
|
||||
* one of the job's processes exited,
|
||||
* but there are more
|
||||
*/
|
||||
if (job->state == JOBRUNNING)
|
||||
continue;
|
||||
} else
|
||||
job = jp; /* we want this, and it is done */
|
||||
|
||||
if (job->flags & JOBWANTED || (*argptr == 0 && any)) {
|
||||
int rv;
|
||||
|
||||
job->flags &= ~JOBWANTED; /* got it */
|
||||
rv = jobstatus(job, 0);
|
||||
VTRACE(DBG_WAIT, (
|
||||
"wanted %d (%s) done: st=%d", i,
|
||||
job->ref ? job->ref : "", rv));
|
||||
if (any || job == last) {
|
||||
retval = rv;
|
||||
fpid = job->ref;
|
||||
|
||||
VTRACE(DBG_WAIT, (" save"));
|
||||
if (pid) {
|
||||
/*
|
||||
* don't need fpid unless we are going
|
||||
* to return it.
|
||||
*/
|
||||
if (fpid == NULL) {
|
||||
/*
|
||||
* this only happens with "wait -n"
|
||||
* (that is, no pid args)
|
||||
*/
|
||||
snprintf(idstring, sizeof idstring,
|
||||
"%d", job->ps[ job->nprocs ?
|
||||
job->nprocs-1 :
|
||||
0 ].pid);
|
||||
fpid = idstring;
|
||||
}
|
||||
VTRACE(DBG_WAIT, (" (for %s)", fpid));
|
||||
}
|
||||
}
|
||||
|
||||
if (job->state == JOBDONE) {
|
||||
VTRACE(DBG_WAIT, (" free"));
|
||||
freejob(job);
|
||||
}
|
||||
|
||||
if (any || (found > 0 && --found == 0)) {
|
||||
if (pid && fpid)
|
||||
setvar(pid, fpid, 0);
|
||||
VTRACE(DBG_WAIT, (" return %d\n", retval));
|
||||
return retval;
|
||||
}
|
||||
VTRACE(DBG_WAIT, ("\n"));
|
||||
continue;
|
||||
}
|
||||
/* loop until process terminated or stopped */
|
||||
while (job->state == JOBRUNNING) {
|
||||
if (dowait(WBLOCK|WNOFREE, job) == -1)
|
||||
return 128 + lastsig();
|
||||
}
|
||||
retval = jobstatus(job, 0);
|
||||
if (!iflag)
|
||||
freejob(job);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* this is to handle "wait" (no args) */
|
||||
if (found == 0 && job->state == JOBDONE) {
|
||||
VTRACE(DBG_JOBS|DBG_WAIT, ("Cleanup: %d\n", i));
|
||||
freejob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
|
@ -802,7 +957,7 @@ getjob(const char *name, int noerror)
|
|||
} else if (name[0] == '%') {
|
||||
if (is_number(name + 1)) {
|
||||
jobno = number(name + 1) - 1;
|
||||
} else if (!name[2]) {
|
||||
} else if (!name[1] || !name[2]) {
|
||||
switch (name[1]) {
|
||||
#if JOBS
|
||||
case 0:
|
||||
|
@ -909,7 +1064,7 @@ makejob(union node *node, int nprocs)
|
|||
INTOFF;
|
||||
jp->state = JOBRUNNING;
|
||||
jp->used = 1;
|
||||
jp->changed = 0;
|
||||
jp->flags = 0;
|
||||
jp->nprocs = 0;
|
||||
jp->pgrp = 0;
|
||||
#if JOBS
|
||||
|
@ -1092,7 +1247,7 @@ waitforjob(struct job *jp)
|
|||
INTOFF;
|
||||
VTRACE(DBG_JOBS, ("waitforjob(%%%d) called\n", jp - jobtab + 1));
|
||||
while (jp->state == JOBRUNNING) {
|
||||
dowait(WBLOCK, jp);
|
||||
dowait(WBLOCK, jp, NULL);
|
||||
}
|
||||
#if JOBS
|
||||
if (jp->jobctl) {
|
||||
|
@ -1144,7 +1299,7 @@ waitforjob(struct job *jp)
|
|||
*/
|
||||
|
||||
STATIC int
|
||||
dowait(int flags, struct job *job)
|
||||
dowait(int flags, struct job *job, struct job **changed)
|
||||
{
|
||||
int pid;
|
||||
int status;
|
||||
|
@ -1155,6 +1310,10 @@ dowait(int flags, struct job *job)
|
|||
int stopped;
|
||||
|
||||
VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags));
|
||||
|
||||
if (changed != NULL)
|
||||
*changed = NULL;
|
||||
|
||||
do {
|
||||
pid = waitproc(flags & WBLOCK, job, &status);
|
||||
VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n",
|
||||
|
@ -1179,12 +1338,14 @@ dowait(int flags, struct job *job)
|
|||
sp->status, status));
|
||||
if (WIFCONTINUED(status)) {
|
||||
if (sp->status != -1)
|
||||
jp->changed = 1;
|
||||
jp->flags |= JOBCHANGED;
|
||||
sp->status = -1;
|
||||
jp->state = 0;
|
||||
} else
|
||||
sp->status = status;
|
||||
thisjob = jp;
|
||||
if (changed != NULL)
|
||||
*changed = jp;
|
||||
}
|
||||
if (sp->status == -1)
|
||||
stopped = 0;
|
||||
|
@ -1208,7 +1369,8 @@ dowait(int flags, struct job *job)
|
|||
}
|
||||
}
|
||||
|
||||
if (thisjob && (thisjob->state != JOBRUNNING || thisjob->changed)) {
|
||||
if (thisjob &&
|
||||
(thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) {
|
||||
int mode = 0;
|
||||
|
||||
if (!rootshell || !iflag)
|
||||
|
@ -1222,7 +1384,7 @@ dowait(int flags, struct job *job)
|
|||
VTRACE(DBG_JOBS,
|
||||
("Not printing status, rootshell=%d, job=%p\n",
|
||||
rootshell, job));
|
||||
thisjob->changed = 1;
|
||||
thisjob->flags |= JOBCHANGED;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $NetBSD: jobs.h,v 1.20 2011/06/18 21:18:46 christos Exp $ */
|
||||
/* $NetBSD: jobs.h,v 1.21 2017/10/28 06:36:17 kre Exp $ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 1991, 1993
|
||||
|
@ -68,6 +68,7 @@ struct procstat {
|
|||
struct job {
|
||||
struct procstat ps0; /* status of process */
|
||||
struct procstat *ps; /* status or processes when more than one */
|
||||
void *ref; /* temporary reference, used variously */
|
||||
int nprocs; /* number of processes */
|
||||
pid_t pgrp; /* process group of this job */
|
||||
char state;
|
||||
|
@ -75,7 +76,9 @@ struct job {
|
|||
#define JOBSTOPPED 1 /* all procs are stopped */
|
||||
#define JOBDONE 2 /* all procs are completed */
|
||||
char used; /* true if this entry is in used */
|
||||
char changed; /* true if status has changed */
|
||||
char flags;
|
||||
#define JOBCHANGED 1 /* set if status has changed */
|
||||
#define JOBWANTED 2 /* set if this is a job being sought */
|
||||
#if JOBS
|
||||
char jobctl; /* job running under job control */
|
||||
int prev_job; /* previous job index */
|
||||
|
|
83
bin/sh/sh.1
83
bin/sh/sh.1
|
@ -1,4 +1,4 @@
|
|||
.\" $NetBSD: sh.1,v 1.169 2017/10/25 05:42:56 kre Exp $
|
||||
.\" $NetBSD: sh.1,v 1.170 2017/10/28 06:36:17 kre Exp $
|
||||
.\" Copyright (c) 1991, 1993
|
||||
.\" The Regents of the University of California. All rights reserved.
|
||||
.\"
|
||||
|
@ -3104,14 +3104,56 @@ The exit status is 0, unless an attempt was made to unset
|
|||
a readonly variable, in which case the exit status is 1.
|
||||
It is not an error to unset (or undefine) a variable (or function)
|
||||
that is not currently set (or defined.)
|
||||
.It wait Op Ar job
|
||||
Wait for the specified job to complete and return the exit status of the
|
||||
last process in the job, or 127 if the job is not a current child of
|
||||
the shell.
|
||||
If the argument is omitted, wait for all jobs to
|
||||
complete and then return an exit status of zero.
|
||||
.It wait Oo Fl n Oc Oo Fl p Ar var Oc Op Ar job ...
|
||||
Wait for the specified jobs to complete
|
||||
and return the exit status of the last job in the parameter list,
|
||||
or 127 if that job is not a current child of the shell.
|
||||
.Pp
|
||||
If no
|
||||
.Ar job
|
||||
arguments are given, wait for all jobs to
|
||||
complete and then return an exit status of zero
|
||||
(including when there were no jobs, and so nothing exited.)
|
||||
.Pp
|
||||
With the
|
||||
.Fl n
|
||||
option, wait instead for any one of the given
|
||||
.Ar job Ns s,
|
||||
or if none are given, any job, to complete, and
|
||||
return the exit status of that job.
|
||||
If none of the given
|
||||
.Ar job
|
||||
arguments is a current child of the shell,
|
||||
or if no
|
||||
.Ar job
|
||||
arguments are given and the shell has no unwaited for children,
|
||||
then the exit status will be 127.
|
||||
.Pp
|
||||
The
|
||||
.Fl p Ar var
|
||||
option allows the process (or job) identifier of the
|
||||
job for which the exit status is returned to be obtained.
|
||||
The variable named (which must not be readonly) will be
|
||||
unset initially, then if a job has exited and its status is
|
||||
being returned, set to the identifier from the
|
||||
arg list (if given) of that job,
|
||||
or the lead process identifier of the job to exit when used with
|
||||
.Fl n
|
||||
and no job arguments.
|
||||
Note that
|
||||
.Fl p
|
||||
with neither
|
||||
.Fl n
|
||||
nor
|
||||
.Ar job
|
||||
arguments is useless, as in that case no job status is
|
||||
returned, the variable named is simply unset.
|
||||
.Pp
|
||||
If the wait is interrupted by a signal,
|
||||
its exit status will be greater than 128.
|
||||
its exit status will be greater than 128,
|
||||
and
|
||||
.Ar var ,
|
||||
if given, will remain unset.
|
||||
.Pp
|
||||
Once waited upon, by specific process number or job-id,
|
||||
or by a
|
||||
|
@ -3119,6 +3161,31 @@ or by a
|
|||
with no arguments,
|
||||
knowledge of the child is removed from the system,
|
||||
and it cannot be waited upon again.
|
||||
.Pp
|
||||
Note than when a list of jobs are given, more that
|
||||
one argument might refer to the same job.
|
||||
In that case, if the final argument represents a job
|
||||
that is also given earlier in the list, it is not
|
||||
defined whether the status returned will be the
|
||||
exit status of the job, or 127 indicating that
|
||||
the child no longer existed when the wait command
|
||||
reached the later argument in the list.
|
||||
In this
|
||||
.Nm
|
||||
the exit status will be that from the job.
|
||||
.Nm
|
||||
waits for each job exactly once, regardless of
|
||||
how many times (or how many different ways) it
|
||||
is listed in the arguments to
|
||||
.Ic wait .
|
||||
That is
|
||||
.Bd -literal -compact
|
||||
wait 100 100 100
|
||||
.Ed
|
||||
is identical to
|
||||
.Bd -literal -compact
|
||||
wait 100
|
||||
.Ed
|
||||
.El
|
||||
.Ss Job Control
|
||||
Each process (or set of processes) started by
|
||||
|
|
Loading…
Reference in New Issue