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:
kre 2017-10-28 06:36:17 +00:00
parent 792031649a
commit 4e9fc30dca
3 changed files with 297 additions and 65 deletions

View File

@ -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;
}
}

View File

@ -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 */

View File

@ -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