How to interrupt Make spawned script? - bash

How to make Ctrl-C interrupt Make spawned script.
My make object spawns a script. While its running, I hit Ctrl-C to interrupt it but there is no effect on it. I have to do Ctrl-Z to suspend the process and then kill %. Is there a way to interrupt the script with Ctrl-C?
I have the following in my Makefile:
run:
bash -c "trap 'trap - SIGINT SIGTERM ERR; exit 1' SIGINT SIGTERM ERR; $(MAKE) run_int"
run_int:
$(MAKE) simulate
simulate:
-$(CALL_TOOL)
CALL_TOOL = external_tool <options>

This is not a make issue, but rather one of the external_program you are launching via make. Presumably it catches and ignores SIGINT.
You can satisfy yourself of this by replacing external_tool <options> in the makefile presented with, say, sleep 30. If you then run make from the terminal, you will find that Ctrl-C (in that terminal) interrupts the execution of sleep just fine:
$ make
bash -c "trap 'trap - SIGINT SIGTERM ERR; exit 1' SIGINT SIGTERM ERR; make run_int"
make[1]: Entering directory `/home/jb/tmp'
make simulate
make[2]: Entering directory `/home/jb/tmp'
sleep 10
^Cmake[2]: *** [simulate] Interrupt
make[1]: *** [run_int] Interrupt
make: *** [run] Error 1
Note also that your run rule is pointless. Trapping SIGINT would be relevant only if that signal were being delivered to the shell in which you define the trap, and it isn't. It's being delivered to the foreground process, which will be the one launched by the simulate rule unless you are superhumanly fast with your Ctrl-C keystroke. You can remove that rule altogether, and still interrupt the process run by the simulate rule. As already discussed, how that process responds to the signal is a separate question.

Related

Issues with `trap cleanup INT EXIT` in BASH

I wanted my BASH script to end in a defined way doing some cleanup before exiting.
It's easy to do if the script runs until end, but it's getting tricky if the user is impatient and sends a SIGINT (^C).
So I added a trap cleanup INT EXIT (cleanup is my function to clean things up), and I thought things were OK (as cleanup would be called when the script exits, cleanup itself does not use exit).
But then I started a test adding kill -INT $$; sleep 4 in the middle of the script, and I realized that cleanup is being called on SIGINT, but still the sleep 4 was executed and at the end of my script cleanup was called a second time, something I did not intend.
So I wanted to "reset" the handlers at the end of my cleanup using trap INT EXIT as the manual page said the syntax is "trap [-lp] [[arg] sigspec ...]" (also saying: "If arg is absent (and there is a single sigspec) or -, each specified signal is reset to its original disposition (the value it had upon entrance to the shell).").
Interestingly that did not work as intended, so I used trap '' INT EXIT instead (The manual says: "If arg is the null string the signal specified by each sigspec is ignored by the shell and by the commands it invokes.").
It would be a nice sub-question how to do it correctly, but let's ignore that right now.
If I modify my trap to trap cleanup INT, then the cleanup is executed immediately when receiving the SIGINT, and not when the script exits after the sleep eventually (SIGINT does not cause the script to exit early).
If I modify my trap to trap cleanup EXIT, then the cleanup is executed immediately when receiving the SIGINT, and the script ends after cleanup returned.
So the question is: Does trap cleanup INT EXIT make any sense (for cleanup purposes)?
It seems to me that EXIT includes the exits caused by any signal, too (I'm unsure whether that has been the case always).
Contrary trapping SIGINT would perform cleanup actions without actually causing the script to exit.
Is there a general agreed-on "cleanup trap pattern"?
(There is a similar question in bash robustness: what is a correct and portable way to trap for the purpose of an "on exit" cleanup routine?, but it has no good answer)
The shell does not exit when a signal for which a trap has been set is received. So, the answer is no; trap cleanup INT EXIT does not make any sense for cleanup purposes, as it prevents SIGINT from interrupting the execution of the program, and hooks the cleanup routine to an event that doesn't warrant a cleanup anymore.
Not sure how agreed-upon, but this is how I do an automatic cleanup on normal or signal-driven termination:
cleanup() {
# do the cleanup
}
trap cleanup EXIT

Makefile: how to run bash script and ignore its exit status?

Minimized test case for the problem:
I have following Makefile:
test:
bash test.sh || true
echo OK
and the test.sh contains
#!/bin/bash
while read -p "Enter some text or press Ctrl+C to exit > " input
do
echo "Your input was: $input"
done
When I run make test and press Ctrl+C to exit the bash read the make will emit
Makefile:2: recipe for target 'test' failed
make: *** [test] Interrupt
How can I tell make to ignore the exit status of the script? I already have || true after the script which usually is enough to get make to keep going but for some reason, the SIGINT interrupting the read will cause make to behave different for this case.
I'm looking for a generic answer that works for processes other than while read loop in bash, too.
This has nothing to do with the exit status of the script. When you press ^C you're sending an interrupt signal to the make program, not just to your script. That causes the make program to stop, just like ^C always does.
There's no way to have make ignore ^C operations; whenever you press ^C at the terminal, make will stop.
ctrl+c sends a signal to the program to tell it to stop. What you want is ctrl+d which sends the signal EOT (end of transmission). You will need to send ctrl+d twice unless you are at the beginning of a line.
some text<c-d><c-d>
or
some text<return>
<c-d>
I found a way to make this work. It's a bit tricky so I'll explain the solution first. The important thing to understand that Ctrl+C is handled by your terminal and not by the currently running process in the terminal as I previously thought. When the terminal catches your Ctrl+C it will check the foreground process group and then send SIGINT to all processes in that group immediately. When you run something via Makefile and press Ctrl+C the SIGINT be immediately sent to Makefile and all processes that it started because those all belong in the foreground process group. And GNU Make handles SIGINT by waiting for any currently executed child process to stop and then exit with a message
Makefile:<line number>: recipe for target '<target>' failed
make: *** [<target>] Interrupt
if the child exited with non-zero exit status. If child handled the SIGINT by itself and exited with status 0, GNU Make will exit silently. Many programs exit via status code 130 on SIGINT but this is not required. In addition, kernel and wait() C API interface can differentiate with status code 130 and status code 130 + child received SIGINT so if Make wanted to behave different for these cases, it would be possible regardless of exit code. bash doesn't support testing for child process SIGINT status but only supports exit status codes.
The solution is to setup processes so that your foreground process group does not include GNU Make while you want to handle Ctrl+C specially. However, as POSIX doesn't define a tool to create any process groups, we have to use bash specific trick: use bash job control to trigger bash to create a new process group. Be warned that this causes some side-effects (e.g. stdin and stdout behaves slightly different) but at least for my case it was good enough.
Here's how to do it:
I have following Makefile (as usual, nested lines must have TAB instead of spaces):
test:
bash -c 'set -m; bash ./test.sh'
echo OK
and the test.sh contains
#!/bin/bash
int_handler()
{
printf "\nReceived SIGINT, quitting...\n" 1>&2
exit 0
}
trap int_handler INT
while read -p "Enter some text or press Ctrl+C to exit > " input
do
echo "Your input was: $input"
done
The set -m triggers creating a new foreground process group and the int_handler takes care of returning successful exit code on exit. Of course, if you want to have some other exit code but zero on Ctrl+C, feel free to any any value suitable. If you want to have something shorter, the child script only needs trap 'exit 0' INT instead of separate function and setup for it.
For additional information:
https://unix.stackexchange.com/a/99134/20336
https://unix.stackexchange.com/a/386856/20336
https://stackoverflow.com/a/18479195/334451
https://www.cons.org/cracauer/sigint.html

Send signals to child processes?

I'm using a Makefile to run Packer. It is extremely important that if I kill a task with Ctrl+C that the signal is passed to the child processes.
As it stands, it appears that GNU Make simply aborts and does not send the TERM signal to the child process. Packer, upon receiving this signal, attempts to clean up resources, which is very important as it can be creating EC2 instances.
I can't reproduce the behavior you're seeing:
$ echo 'all: ; #echo sleeping; sleep 600' | make -f-
sleeping
^C
make: *** [/tmp/GmjcnM8Y:1: all] Interrupt
$ ps -aef | grep sleep
After the C-c, the sleep program has also been killed.
So, you'll have to provide more details.

Forwarding signals in bash script which is submitted on the cluster

I have a launch.sh script which I submit on the cluster with
bsub $settings < launch.sh
This launch.sh bash script looks simplified as the following:
function trap_with_arg() {
func="$1" ; shift
for sig ; do
echo "$ES Installing trap for signal $sig"
trap "$func $sig" "$sig"
done
}
function signalHandler() {
# do stuff depending in what stage the script is
}
# Setup the Trap
trap_with_arg signalHandler SIGINT SIGTERM SIGUSR1 SIGUSR2
./start.sh
mpirun process.sh
./end.sh
Where process.sh calls two binaries (as an example) as
./binaryA
./binaryB
My question is the following:
The cluster already sends SIGUSR1 (approx. 10min before SIGTERM) to the process (I think this is the bash shell running my launch.sh script).
At the moment I catch this signal in the launch.sh script and call some signal handler. The problem is, this signal handler only gets executed (at least what I know) after a running command is finished (e.g. that might be mpirun process.sh or ./start.sh )
How can I forward these signals to make the commands/binaries exit gracefully. Forwarding for example to process.sh (mpirun, as I experienced, already forwards somehow these received signals (how does it do that?)
What is the proper way of forwarding signals, (e.g. also to the binaries binaryA, binaryB ?
I have no really good clue how to do this? Making the commands execute in background, creating a child process?
Thanks for some enlightenment :-)
From bash manual at http://www.gnu.org/software/bash/manual/html_node/Signals.html:
If Bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes. When Bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.
Thus, the solution seems to place commands in background and use "wait":
something &
wait

shell script process termination issue

/bin/sh -version
GNU sh, version 1.14.7(1)
exitfn () {
# Resore signal handling for SIGINT
echo "exiting with trap" >> /tmp/logfile
rm -f /var/run/lockfile.pid # Growl at user,
exit # then exit script.
}
trap 'exitfn; exit' SIGINT SIGQUIT SIGTERM SIGKILL SIGHUP
The above is my function in shell script.
I want to call it in some special conditions...like
when:
"kill -9" fires on pid of this script
"ctrl + z" press while it is running on -x mode
server reboots while script is executing ..
In short, with any kind of interrupt in script, should do some action
eg. rm -f /var/run/lockfile.pid
but my above function is not working properly; it works only for terminal close or "ctrl + c"
Kindly don't suggest to upgrade "bash / sh" version.
SIGKILL cannot be trapped by the trap command, or by any process. It is a guarenteed kill signal, that by it's definition cannot be trapped. Thus upgrading you sh/bash will not work anyway.
You can't trap kill -9 that's the whole point of it, to destroy processes violently that don't respond to other signals (there's a workaround for this, see below).
The server reboot should first deliver a signal to your script which should be caught with what you have.
As to the CTRL-Z, that also gives you a signal, SIGSTOP from memory, so you may want to add that. Though that wouldn't normally be a reason to shut down your process since it may be then put into the background and restarted (with bg).
As to what do do for those situations where your process dies without a catchable signal (like the -9 case), the program should check for that on startup.
By that, I mean lockfile.pid should store the actual PID of the process that created it (by using echo $$ >/var/run/myprog_lockfile.pid for example) and, if you try to start your program, it should check for the existence of that process.
If the process doesn't exist, or it exists but isn't the right one (based on name usually), your new process should delete the pidfile and carry on as if it was never there. If the old process both exists and is the right one, your new process should log a message and exit.

Resources