diff --git a/doc/es.1 b/doc/es.1 index dcbd487b..e9822e43 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -1892,7 +1892,7 @@ as input to and executes its contents. The options are a subset of the invocation options for the shell (see below). .TP -.Cr "access \fR[\fP-n \fIname\fP\fR]\fP \fR[\fP-1e\fR]\fP \fR[\fP-rwx\fR]\fP \fR[\fP-fdcblsp\fR]\fP \fIpath ...\fP" +.Cr "access \fR[\fP-n \fIname\fP\fR]\fP \fR[\fP-1e\fR]\fP \fR[\fP-rwx\fR]\fP \fR[\fP-fdcblsp\fR]\fP \fIpath ...\fP" Tests if the named paths are accessible according to the options presented. Normally, .Cr access @@ -2228,7 +2228,7 @@ are specified, .Cr \-e is used. .TP -.Cr "wait \fI\fR[\fPpid\fR]" +.Cr "wait \fR[\fP-n\fR]\fP \fI\fR[\fPpid\fR]" Waits for the specified .IR pid , which must have been started by @@ -2236,6 +2236,11 @@ which must have been started by If no .I pid is specified, waits for any child process to exit. +If +.Cr \-n +is specified and the process(es) to be waited for is (are) not ready +at call time, returns immediately instead of blocking or throwing an +error. .TP .Cr "whatis \fIprogram ...\fP" For each named @@ -2329,6 +2334,19 @@ copied (via to file descriptor .IR newfd . .TP +.Cr "%echo-status \fIpid did status\fP" +Inspects the exit +.I status +of an exited (and waited-for) process +.IR pid , +where +.I did +indicates if the process +.Cr exited +or was +.Cr signaled , +and prints any interesting condition. +.TP .Cr "%eval-noprint \fIcmd\fP" Run the command. Used as the value of @@ -2767,7 +2785,7 @@ variable, which signals to the library how much of the history file should be read into the in-memory history log. .PP -Several primitives are not directly associated with other function. +Several primitives are not directly associated with other functions. They are: .TP .Cr "$&collect" @@ -2777,7 +2795,7 @@ The garbage collector in runs rather frequently; there should be no reason for a user to issue this command. .TP -.Cr "$&noreturn \fIlambda args ...\fP" +.Cr "$&noreturn \fIlambda args ..." Call the .IR lambda , but in such a way that it does not catch the @@ -2792,6 +2810,11 @@ and .Cr "&&" ) can be implemented as lambdas rather than primitives. .TP +.Cr "$&sigmessage" +converts a signal name such as +.Cr sigint +to a short message describing the signal, used for error reporting. +.TP .Cr "$&primitives" Returns a list of the names of es primitives. .TP diff --git a/es.h b/es.h index 472e88ee..2e5218e9 100644 --- a/es.h +++ b/es.h @@ -222,12 +222,12 @@ extern char *checkexecutable(char *file); /* proc.c */ extern Boolean hasforked; -extern int efork(Boolean parent, Boolean background); +extern int efork(Boolean parent); extern pid_t spgrp(pid_t pgid); extern int tctakepgrp(void); extern void initpgrp(void); -extern int ewait(int pid, Boolean interruptible); -#define ewaitfor(pid) ewait(pid, FALSE) +extern int ewait(int pid, int opts); +#define ewaitfor(pid) ewait(pid, 0) #if JOB_PROTECT extern void tcreturnpgrp(void); diff --git a/eval.c b/eval.c index 8e36896b..f55a6f88 100644 --- a/eval.c +++ b/eval.c @@ -28,7 +28,7 @@ extern List *forkexec(char *file, List *list, Boolean inchild) { Vector *env; gcdisable(); env = mkenv(); - pid = efork(!inchild, FALSE); + pid = efork(!inchild); if (pid == 0) { execve(file, vectorize(list)->vector, env->vector); failexec(file, list); @@ -41,7 +41,6 @@ extern List *forkexec(char *file, List *list, Boolean inchild) { sigint_newline = TRUE; } else SIGCHK(); - printstatus(0, status); return mklist(mkterm(mkstatus(status), NULL), NULL); } diff --git a/initial.es b/initial.es index 452c18d3..1edf9ddd 100644 --- a/initial.es +++ b/initial.es @@ -76,7 +76,6 @@ fn-newpgrp = $&newpgrp fn-result = $&result fn-throw = $&throw fn-umask = $&umask -fn-wait = $&wait fn-%read = $&read @@ -579,6 +578,26 @@ fn %pathsearch name { access -n $name -1e -xf $path } if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} +# The %echo-status hook is used to print any potentially interesting +# status from an exec()ed binary. + +fn %echo-status pid did status { + if {~ $did signaled && !~ $status (sigint sigpipe)} { + let (msg = <={if {$echo-status-pid} {result $pid^': '} {result ''}}) { + msg = $msg^<={$&sigmessage $status} + if {~ $status *+core} { + msg = $msg^'--core dumped' + } + echo >[1=2] $msg + } + } +} + +echo-status-pid = false + +fn wait { + local (echo-status-pid = true) $&wait $* +} # # Read-eval-print loops @@ -642,7 +661,7 @@ fn %interactive-loop { $fn-%dispatch false } {~ $e signal} { if {!~ $type sigint sigterm sigquit} { - echo >[1=2] caught unexpected signal: $type + echo >[1=2] caught unexpected signal: $type: <={$&sigmessage $type} } } { echo >[1=2] uncaught exception: $e $type $msg @@ -756,7 +775,7 @@ max-eval-depth = 640 # is does. fn-%dispatch is really only important to the current # interpreter loop. -noexport = noexport pid signals apid bqstatus fn-%dispatch path home matchexpr +noexport = noexport pid signals apid bqstatus fn-%dispatch path home matchexpr print-status-pid # diff --git a/mksignal b/mksignal index cab97cb5..870733b8 100755 --- a/mksignal +++ b/mksignal @@ -34,12 +34,14 @@ sed -n ' mesg["SIGHUP"] = "hangup" mesg["SIGILL"] = "illegal instruction" mesg["SIGINFO"] = "information request" + mesg["SIGINT"] = "interrupt" mesg["SIGIO"] = "input/output possible" mesg["SIGIOT"] = "IOT instruction" mesg["SIGKILL"] = "killed" mesg["SIGLOST"] = "resource lost" mesg["SIGLWP"] = "lightweight process library signal" mesg["SIGMIGRATE"] = "migrate process" + mesg["SIGPIPE"] = "broken pipe" mesg["SIGPOLL"] = "pollable event occurred" mesg["SIGPROF"] = "profiling timer alarm" mesg["SIGPWR"] = "power failure" @@ -82,13 +84,6 @@ sed -n ' mesg["SIGVIRT"] = "virtual time alarm" mesg["SIGWINDOW"] = "window size changed" - - # set nomesg["SIGNAME"] to suppress message printing - - nomesg["SIGINT"] = 1 - nomesg["SIGPIPE"] = 1 - - # set ignore["SIGNAME"] to explicitly ignore a named signal (usually, this # is just for things that look like signals but really are not) @@ -117,7 +112,7 @@ sed -n ' sig[$1] == 0 && ignore[$1] == 0 { sig[$1] = ++nsig signame[nsig] = $1 - if (mesg[$1] == "" && nomesg[$1] == 0) { + if (mesg[$1] == "") { str = $3 for (i = 4; i <= NF; i++) str = str " " $i diff --git a/prim-io.c b/prim-io.c index 6ded4995..18ec69a2 100644 --- a/prim-io.c +++ b/prim-io.c @@ -143,7 +143,7 @@ static int pipefork(int p[2], int *extra) { registerfd(extra, FALSE); ExceptionHandler - pid = efork(TRUE, FALSE); + pid = efork(TRUE); CatchExceptionIf (pid != 0, e) unregisterfd(&p[0]); unregisterfd(&p[1]); @@ -233,7 +233,7 @@ PRIM(pipe) { for (;; list = list->next) { int p[2], pid; - pid = (list->next == NULL) ? efork(TRUE, FALSE) : pipefork(p, &inpipe); + pid = (list->next == NULL) ? efork(TRUE) : pipefork(p, &inpipe); if (pid == 0) { /* child */ if (inpipe != -1) { @@ -262,9 +262,9 @@ PRIM(pipe) { Ref(List *, result, NULL); do { + int pid = pids[--n]; Term *t; - int status = ewaitfor(pids[--n]); - printstatus(0, status); + int status = ewaitfor(pid); t = mkstr(mkstatus(status)); result = mklist(t, result); } while (0 < n); @@ -275,7 +275,7 @@ PRIM(pipe) { #if HAVE_DEV_FD PRIM(readfrom) { - int pid, p[2], status; + int pid, p[2]; Push push; caller = "$&readfrom"; @@ -307,15 +307,14 @@ PRIM(readfrom) { EndExceptionHandler close(p[0]); - status = ewaitfor(pid); - printstatus(0, status); + ewaitfor(pid); varpop(&push); RefEnd3(cmd, input, var); RefReturn(lp); } PRIM(writeto) { - int pid, p[2], status; + int pid, p[2]; Push push; caller = "$&writeto"; @@ -347,8 +346,7 @@ PRIM(writeto) { EndExceptionHandler close(p[1]); - status = ewaitfor(pid); - printstatus(0, status); + ewaitfor(pid); varpop(&push); RefEnd3(cmd, output, var); RefReturn(lp); @@ -393,11 +391,10 @@ PRIM(backquote) { } close(p[1]); - gcdisable(); lp = bqinput(sep, p[0]); close(p[0]); status = ewaitfor(pid); - printstatus(0, status); + gcdisable(); lp = mklist(mkstr(mkstatus(status)), lp); gcenable(); list = lp; diff --git a/prim-sys.c b/prim-sys.c index c0b4f982..09217c74 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -36,7 +36,7 @@ PRIM(newpgrp) { } PRIM(background) { - int pid = efork(TRUE, TRUE); + int pid = efork(TRUE); if (pid == 0) { #if JOB_PROTECT /* job control safe version: put it in a new pgroup, if interactive. */ @@ -51,12 +51,11 @@ PRIM(background) { PRIM(fork) { int pid, status; - pid = efork(TRUE, FALSE); + pid = efork(TRUE); if (pid == 0) esexit(exitstatus(eval(list, NULL, evalflags | eval_inchild))); status = ewaitfor(pid); SIGCHK(); - printstatus(0, status); return mklist(mkstr(mkstatus(status)), NULL); } @@ -128,6 +127,26 @@ PRIM(setsignals) { return mksiglist(); } +PRIM(sigmessage) { + int sig; + char *s, *p; + if (list == NULL || list->next != NULL) + fail("$&sigmessage", "usage: $&sigmessage signal"); + s = getstr(list->term); + if ((p = strchr(s, '+')) != NULL) { + if (streq(p, "+core")) + *p = '\0'; + else + p = NULL; + } + sig = signumber(s); + if (p != NULL) + *p = '+'; + if (sig < 0) + fail("$&sigmessage", "unknown signal: %s", s); + return mklist(mkstr(sigmessage(sig)), NULL); +} + /* * limit builtin -- this is too much code for what it gives you */ @@ -314,13 +333,12 @@ PRIM(time) { getrusage(RUSAGE_CHILDREN, &ru_prev); gc(); /* do a garbage collection first to ensure reproducible results */ t0 = time(NULL); - pid = efork(TRUE, FALSE); + pid = efork(TRUE); if (pid == 0) esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); - status = ewait(pid, FALSE); + status = ewait(pid, 0); t1 = time(NULL); SIGCHK(); - printstatus(0, status); getrusage(RUSAGE_CHILDREN, &ru_new); timesub(&ru_new.ru_utime, &ru_prev.ru_utime, &ru_diff.ru_utime); @@ -343,7 +361,7 @@ PRIM(time) { Ref(List *, lp, list); gc(); /* do a garbage collection first to ensure reproducible results */ - pid = efork(TRUE, FALSE); + pid = efork(TRUE); if (pid == 0) { clock_t t0, t1; struct tms tms; @@ -353,14 +371,13 @@ PRIM(time) { ticks = sysconf(_SC_CLK_TCK); t0 = times(&tms); - pid = efork(TRUE, FALSE); + pid = efork(TRUE); if (pid == 0) esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); status = ewaitfor(pid); t1 = times(&tms); SIGCHK(); - printstatus(0, status); tms.tms_cutime += ticks / 20; tms.tms_cstime += ticks / 20; @@ -376,7 +393,6 @@ PRIM(time) { } status = ewaitfor(pid); SIGCHK(); - printstatus(0, status); RefEnd(lp); return mklist(mkstr(mkstatus(status)), NULL); @@ -457,6 +473,7 @@ extern Dict *initprims_sys(Dict *primdict) { X(fork); X(run); X(setsignals); + X(sigmessage); #if BSD_LIMITS X(limit); #endif diff --git a/proc.c b/proc.c index d8c9faca..c60ed650 100644 --- a/proc.c +++ b/proc.c @@ -2,12 +2,14 @@ #include "es.h" +#define EWINTERRUPTIBLE 1 +#define EWNOHANG 2 + Boolean hasforked = FALSE; typedef struct Proc Proc; struct Proc { int pid; - Boolean background; Proc *next, *prev; }; @@ -20,22 +22,21 @@ static pid_t tcpgid0; #endif /* mkproc -- create a Proc structure */ -extern Proc *mkproc(int pid, Boolean background) { +extern Proc *mkproc(int pid) { Proc *proc = ealloc(sizeof (Proc)); proc->next = proclist; proc->pid = pid; - proc->background = background; proc->prev = NULL; return proc; } /* efork -- fork (if necessary) and clean up as appropriate */ -extern int efork(Boolean parent, Boolean background) { +extern int efork(Boolean parent) { if (parent) { int pid = fork(); switch (pid) { default: { /* parent */ - Proc *proc = mkproc(pid, background); + Proc *proc = mkproc(pid); if (proclist != NULL) proclist->prev = proc; proclist = proc; @@ -117,13 +118,13 @@ extern Noreturn esexit(int code) { #endif /* dowait -- a waitpid wrapper that interfaces with signals */ -static int dowait(int pid, int *statusp) { +static int dowait(int pid, int opts, int *statusp) { int n; interrupted = FALSE; if (!setjmp(slowlabel)) { slow = TRUE; n = interrupted ? -2 : - waitpid(pid, statusp, 0); + waitpid(pid, statusp, (opts & EWNOHANG ? WNOHANG : 0)); } else n = -2; slow = FALSE; @@ -151,23 +152,29 @@ static Proc *reap(int pid) { } /* ewait -- wait for a specific process to die, or any process if pid == -1 */ -extern int ewait(int pidarg, Boolean interruptible) { +extern int ewait(int pidarg, int opts) { int deadpid, status; Proc *proc; - while ((deadpid = dowait(pidarg, &status)) == -1) { - if (errno == ECHILD && pidarg > 0) - fail("es:ewait", "wait: %d is not a child of this shell", pidarg); - else if (errno != EINTR) + while ((deadpid = dowait(pidarg, (opts & EWNOHANG), &status)) == -1) { + if (errno == ECHILD) { + if (pidarg > 0) + fail("es:ewait", "wait: %d is not a child of this shell", pidarg); + if ((opts & EWNOHANG) > 0) { + deadpid = 0; + break; + } + } else if (errno != EINTR) fail("es:ewait", "wait: %s", esstrerror(errno)); - if (interruptible) + if (opts & EWINTERRUPTIBLE) SIGCHK(); } - proc = reap(deadpid); #if JOB_PROTECT tctakepgrp(); #endif - if (proc->background) - printstatus(proc->pid, status); + if (deadpid == 0) /* dowait(EWNOHANG) returned nothing */ + return -1; + proc = reap(deadpid); + printstatus(proc->pid, status); efree(proc); return status; } @@ -177,30 +184,38 @@ extern int ewait(int pidarg, Boolean interruptible) { PRIM(apids) { Proc *p; Ref(List *, lp, NULL); - for (p = proclist; p != NULL; p = p->next) - if (p->background) { - Term *t = mkstr(str("%d", p->pid)); - lp = mklist(t, lp); - } + for (p = proclist; p != NULL; p = p->next) { + Term *t = mkstr(str("%d", p->pid)); + lp = mklist(t, lp); + } /* TODO: sort the return value, but by number? */ RefReturn(lp); } PRIM(wait) { - int pid; - if (list == NULL) - pid = -1; - else if (list->next == NULL) { - pid = atoi(getstr(list->term)); + int status, pid = -1, opts = EWINTERRUPTIBLE; + Ref(List *, lp, list); + if (lp != NULL && streq(getstr(lp->term), "-n")) { + opts = opts | EWNOHANG; + lp = lp->next; + } + if (lp != NULL) { + pid = atoi(getstr(lp->term)); if (pid <= 0) { fail("$&wait", "wait: %d: bad pid", pid); NOTREACHED; } - } else { - fail("$&wait", "usage: wait [pid]"); + lp = lp->next; + } + if (lp != NULL) { + fail("$&wait", "usage: wait [-n] [pid]"); NOTREACHED; } - return mklist(mkstr(mkstatus(ewait(pid, TRUE))), NULL); + RefEnd(lp); + status = ewait(pid, opts); + if (status == -1) /* ewait got no exited processes */ + return NULL; + return mklist(mkstr(mkstatus(status)), NULL); } extern Dict *initprims_proc(Dict *primdict) { diff --git a/status.c b/status.c index 13dd47ed..2ddca3ce 100644 --- a/status.c +++ b/status.c @@ -65,18 +65,15 @@ extern char *mkstatus(int status) { /* printstatus -- print the status if we should */ extern void printstatus(int pid, int status) { - if (WIFSIGNALED(status)) { - const char *msg = sigmessage(WTERMSIG(status)), *tail = ""; - if (WCOREDUMP(status)) { - tail = "--core dumped"; - if (*msg == '\0') - tail += (sizeof "--") - 1; - } - if (*msg != '\0' || *tail != '\0') { - if (pid == 0) - eprint("%s%s\n", msg, tail); - else - eprint("%d: %s%s\n", pid, msg, tail); - } + Ref(List *, fn, varlookup("fn-%echo-status", NULL)); + Ref(List *, list, NULL); + if (fn != NULL) { + gcdisable(); + list = mklist(mkstr(mkstatus(status)), NULL); + list = mklist(mkstr((WIFSIGNALED(status) ? "signaled" : "exited")), list); + list = mklist(mkstr(str("%d", pid)), list); + gcenable(); + eval(append(fn, list), NULL, 0); } + RefEnd2(list, fn); }