[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [bug-gawk] system() should return != 0 when the process is killed
From: |
Stephane Chazelas |
Subject: |
Re: [bug-gawk] system() should return != 0 when the process is killed |
Date: |
Fri, 4 Mar 2016 22:50:13 +0000 |
User-agent: |
Mutt/1.5.21 (2010-09-15) |
2016-03-03 17:58:25 +0100, Tobia Conforto:
[...]
> It looks like gawk's system() only returns bits 8 to 15 (or s>>8) of the
> value returned by system(3), discarding any information about the
> termination of the child process by a signal.
>
> This breaks use cases like this (and probably others):
>
> do {
> ...
> } while (! system("sleep 10"))
>
> where the intention is to break the loop when the user interrupts (^C) the
> child process.
>
> It would arguably be better if gawk returned a composite code, as
> traditionally done by most shells and interpreters, for example (s&127)+128
> if killed, s>>8 otherwise, as in Bash.
>
> Other awk implementations (nawk, bwk-awk, and mawk) always return values !=
> 0 when the child process executed by system() is killed by a signal.
[...]
Note that there's also a problem with close() on a getline
pipeline.
That's probably something that should be clarified in the POSIX
spec first. Or gawk could go with the mawk approach which seems
the most consistent to me.
See also:
http://unix.stackexchange.com/questions/267415/make-bash-exit-0-when-called-by-awk-and-interrupted-with-c/267424#267424
for more context, reproduced below:
What awk's system() should return [1]is poorly specified.
What seems to be common among awk implementations is that upon a normal
exit, it returns the exit code (the number passed to exit(3) modulo 256),
but when the shell process is killed by a signal, there's a lot of
different behaviour.
Also note that while the C function system(3) is meant to ignore SIGINT
(and SIGQUIT) in the parent, it's not very clear (to me at least) that the
requirement also applies to awk's system(). Some awk implementations (like
mawk) will die upon that SIGINT (that's also the behaviour I'd like to see
as I don't like my CTRL-C being ignored just because awk happens to be
running the system() function), some (like gawk or traditional
implementations) won't.
Also note that some shells can intercept some of those signals and
eventually call exit() which would affect the behaviour (see the
discussion in comments about the Bourne shell for instance), which is why
I use exec in the examples below to remove the shell from the loop.
For the value returned by system() (there's even more variation if you
consider close()^1) upon a SIGINT, we see:
$ nawk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ bwk-awk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ mawk 'BEGIN {print system("exec kill -s INT $$")}'
130
$ gawk 'BEGIN {print system("exec kill -s INT $$")}'
0
0.0078125 being 2 / 256 (for a SEGV of 11, you'd get 0.542969
((128+11)/256) if a core was dumped, 0.0429688 (11/256) otherwise), nawk
being the nawk found on Solaris 10 or 11, or its Linux port in the
Heirloom toolchest, bwk-awk being [2]the awk maintained by Brian Kernighan
himself (the K in awk) the basis for the awk found on some BSDs (here
tested on Debian GNU/Linux). /usr/xpg4/bin/awk on Solaris 11 behaves like
gawk.
So based on the value s returned by system(3) (an integer where bits 0 to
6 are the signal number, bit 7 the core-bit, and bits 8 to 15 the exit
code), awk's system() above returns either:
* s / 256 (traditional awk implementations),
* int(s/256) (gawk),
* or in mawk, the same transformation as done by shells like the Bourne
or C-shell for instance ((s&127)+128 if killed, s>>8 otherwise),
except that if a core is dumped, you get (s&127)+256 instead of
(s&127)+128.
So, here, you could do instead:
awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10")}'
But it would still cause awk to be killed with some awk implementations
like mawk. If your sh is bash or yash, you could do:
awk 'BEGIN{print system("set -m; sleep 10; exit")}'
So sleep is run in its own process group (and only it gets the SIGINT).
Another alternative could be to ignore SIGINT before calling awk. However,
most shells (and that's a POSIX requirement) cannot change a signal
handler if the signal was already ignored on start. So things like:
(
trap '' INT
awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10; exit")}'
)
Won't work. zsh doesn't have that (self-inflicted) limitation though, so
if you know zsh is available, you could do:
(
trap '' INT
awk 'BEGIN{print system("exec zsh -c \"TRAPINT() exit 1; sleep 10\"")}'
)
Which would work whether awk is mawk, gawk or other and would avoid having
to mess with job control. At this point though, it would be worth
considering using perl/python/ruby... instead of awk where you can adapt
the signal handling to your needs.
Notes
^1 Upon close() of a pipeline, as in:
awk 'BEGIN {cmd = "kill -s INT $$"; cmd | getline; print close(cmd)}'
First, this time ^C interrupts awk in all implementations I've tried
(there's not such requirement to ignore SIGINT/SIGQUIT for popen(3) (a
natural way to implement that getline) as there is for system(3)).
But when it comes to the exit status (where s is the value returned by
pclose(3)/waitpid(2) like for system() above), we see:
* Solaris nawk: doesn't work, you can't call close() like that in
Solaris nawk.
* /usr/xpg4/bin/awk on Solaris. Returns always 0, even upon a exit(1)
done by the process. Clearly a conformance bug.
* gawk: returns s&255 if killed (includes the core bit), s>>8 otherwise.
* bwk-awk: gives s (exit 1 gives 256, killed by SIGINT gives 2, killed
by SIGSEGV with core gives 139).
* mawk: same as for system(), looks like mawk is the only implementation
that gave any thought into that.
References
Visible links
1. http://austingroupbugs.net/view.php?id=947#c2657
2. http://www.cs.princeton.edu/%7Ebwk/btl.mirror/
--
Stephane
Re: [bug-gawk] system() should return != 0 when the process is killed,
Stephane Chazelas <=
Re: [bug-gawk] system() should return != 0 when the process is killed, Aharon Robbins, 2016/03/07
Re: [bug-gawk] system() should return != 0 when the process is killed, Aharon Robbins, 2016/03/10