Why is "Doing an exit 130 is not the same as dying of SIGINT"?

9,239

The 130 (128+SIGINT) you see in $? after the last command died of a SIGINT is a simplified representation of its exit status made by some shells like bash. Other shells will use different representations (like 256+signum in ksh93, 128+256+signum in yash, textual representations like sigint or sigquit+core in rc/es). See Default exit code when process is terminated? for more details on that.

A process can wait for its child process and query its status:

  • if it was stopped (with which signal)
  • if it was resumed
  • if it was killed (with which signal)
  • if it has trapped (for ptraced processes)
  • if it dumped a core
  • if it exited normally with the _exit() system call (with which exit code)

To do that, they use one of the wait(), waitpid(), waitid() (see also obsolete wait3(), wait4()) or a handler on the SIGCHLD system call.

Those system calls return all that information above. (Except for waitid() on some system, only the lowest 8 bits of the number passed to _exit() for child that terminate normally are available though).

But bash (and most Bourne-like and csh-like shells) bundle all that information in a 8 bit number for $? ($? is the lowest 8 bits of the exit code for processes that terminate normally, and 128+signum it it was killed or suspended or trapped, all the other information is not available). So obviously, there's some information being lost. In particular, through $? alone, one can't tell if a process did a _exit(130) or died of a SIGINT.

bash knows when a process is being killed obviously. For example when background processes are killed, you see:

[1]+  Interrupt               sleep 20

But in $?, it doesn't give you enough information to tell whether it was killed by SIGINT or it called _exit(130).

Since most shells do that transformation, applications know better than doing _exit(number_greater_than_127) for anything but reporting a death by signal though.

Still if a process does a _exit(130), the process waiting for that process will detect that that process terminated normally, not that it was killed by a signal. In C, WIFEXITED() will return true, WIFSIGNALED() will return false.

bash itself will not consider the process as having died of a SIGINT (even though it lets you think it might have through $? containing the same value as if it had died of a SIGINT).

So, that will not trigger the special handling of SIGINT that bash does. In a script, both bash and the currently running command in the script will receive a SIGINT upon ^C (as they're both in the same process group).

bash dies upon receiving SIGINT only if the command it is waiting for also died of a SIGINT (the idea being that if for instance in your script, you run vi or less and use ^C to abort something there which doesn't make vi/less die, your script doesn't die upon returning quitting vi/less later on).

If that command bash is waiting for does a _exit(130) in a handler of SIGINT, bash will not die upon that SIGINT (it will not consider itself as having been interrupted because it doesn't believe the child has been interrupted).

That's why when you want to report a death by SIGINT, that you have indeed been interrupted even though you are actually doing some extra processing upon receiving that signal in a handler, you should not do a _exit(130), but actually kill yourself with SIGINT (after having restored the default handler for SIGINT). In a shell, that's with:

trap '
  extra processing
  trap - INT # restore SIGINT handler
  kill -s INT "$$" # report to the parent that we have indeed been
                   # interrupted
  ' INT
Share:
9,239

Related videos on Youtube

Tim
Author by

Tim

Elitists are oppressive, anti-intellectual, ultra-conservative, and cancerous to the society, environment, and humanity. Please help make Stack Exchange a better place. Expose elite supremacy, elitist brutality, and moderation injustice to https://stackoverflow.com/contact (complicit community managers), in comments, to meta, outside Stack Exchange, and by legal actions. Push back and don't let them normalize their behaviors. Changes always happen from the bottom up. Thank you very much! Just a curious self learner. Almost always upvote replies. Thanks for enlightenment! Meanwhile, Corruption and abuses have been rampantly coming from elitists. Supportive comments have been removed and attacks are kept to control the direction of discourse. Outright vicious comments have been removed only to conceal atrocities. Systematic discrimination has been made into policies. Countless users have been harassed, persecuted, and suffocated. Q&A sites are for everyone to learn and grow, not for elitists to indulge abusive oppression, and cover up for each other. https://softwareengineering.stackexchange.com/posts/419086/revisions https://math.meta.stackexchange.com/q/32539/ (https://i.stack.imgur.com/4knYh.png) and https://math.meta.stackexchange.com/q/32548/ (https://i.stack.imgur.com/9gaZ2.png) https://meta.stackexchange.com/posts/353417/timeline (The moderators defended continuous harassment comments showing no reading and understanding of my post) https://cs.stackexchange.com/posts/125651/timeline (a PLT academic had trouble with the books I am reading and disparaged my self learning posts, and a moderator with long abusive history added more insults.) https://stackoverflow.com/posts/61679659/revisions (homework libels) Much more that have happened.

Updated on September 18, 2022

Comments

  • Tim
    Tim over 1 year

    From Stéphane Chazelas's reply at https://unix.stackexchange.com/a/230568

    Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).

    However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.

    1. Regarding "Doing an exit 130 is not the same as dying of SIGINT", what are the differences between them?
    2. Why is it that "for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT"?

    From Bash manual:

    When a command terminates on a fatal signal whose number is N, Bash uses the value 128+N as the exit status

    The signal number of SIGINT is 2, so the exit status of a command terminating on SIGINT is 130. So it seems to me that doing an exit 130 is the same as dying of SIGINT, and that 130 exit status is considered as a death by SIGINT.

    Thanks.

  • JdeBP
    JdeBP over 5 years
    Interestingly, Jörg Schilling's bosh does not conflate the exit code and exit status into a single variable. unix.stackexchange.com/a/242116/5132