Trapping a daemon

Before giving yourselves to the black magic of creating a daemon, you should learn how to shield it from any signals that can doom it to death. As we saw in the previous chapters, if a process dies, it could leave a mess behind since it had no time to clean the house. Scary, but we can do something to prevent all this: using traps that will help us deal with the signal and create more robust and well functioning scripts. In our case, the trap built-in will be handy to keep an eye on how our script behaves, since it is a signal handler that modifies how a process reacts to a signal. The general syntax of trap is here:

trap commands signal_list

With commands being a list that can be executed, functions included, upon receiving a signal. We already saw some of the signals and their numeric values, but trap can use some keywords for the most common ones, as listed in the following table:

Signal

Numeric value

HUP

1

Hang up. Means that the controlling terminal exited.

INT

2

Interrupt, it happens when Ctrl + C is pressed.

QUIT

3

Quit.

KILL

9

This is an untrappable signal. Upon receiving, the process has to exit.

TERM

15

Terminate, is the default kill signal, can be handled, otherwise the process exits gracefully.

EXIT

0

An exit trap is raised on exit.

 

You can specify one or more signals per single trap, and you can also reset a trap to its default behavior using the trap called – signal.
Signals, how many of them? Who can remember all of them? No one but the kill command:

zarrelli:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7
42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11
46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

Now, let's see how to use a trap for a clean exit with this little example:

#!/bin/bash
x=0
while true
do
for i in {1..1000}
do
x="$i"
if (( x == 500 ))
then
echo "The value of x is: $x" >> write.log
fi
done
done

This script features an infinite while loop, which hosts a nested for loop, running through a range between 1 and 1000. When the value of x reaches 500 , it prints a message on the write.log file. Upon exit, the inner loop is relaunched, but the outer structure is an infinite loop and will keep running indefinitely. Let's run it and after a few seconds, let's issue a Ctrl + C:

zarrelli:~$ ./write.sh 
^C

So, we had the terminal locked by our script that was running in the foreground, and to regain control, we had to issue a kill -15 , a TERM signal, by pressing Ctrl+C. Let's have a look at the directory:

zarrelli:~$ ls -lh
total 28K
-rw-r--r-- 1 zarrelli zarrelli 20 Apr 16 07:54 open
-rwxr--r-- 1 zarrelli zarrelli 193 Apr 16 13:27 test.sh
-rwxr--r-- 1 zarrelli zarrelli 35 Apr 16 11:54 while.sh
-rw-r--r-- 1 zarrelli zarrelli 5.2K Apr 16 13:32 write.log
-rwxr-xr-x 1 zarrelli zarrelli 152 Apr 16 13:05 write.sh
-rwxr-xr-x 1 zarrelli zarrelli 293 Apr 16 13:28 write-term.sh

It seems that the log was left behind:

zarrelli:~$ tail -5 write.log 
The value of x is: 500
The value of x is: 500
The value of x is: 500
The value of x is: 500
The value of x is: 500

Yes, it is actually our log filled with the message we set up. Being a log, it is not so bad if it is left behind, but what if this were a temporary file? Would we want to litter the filesystem with temp files each time the script exits because of a term or another signal? Let's improve it creating a cleanup function:

clean_exit()
{
echo "ouch, we received a iINT signal. Outta here but first a bit of cleaning"
rm write.log
exit 0
}

Once invoked, this function will echo a meaningful message on stdout, delete the write.log file, and exit with a successful status. The last bit is the actual signal handler:

trap 'clean_exit' INT

That is all, let's run the script and give a Ctrl+C after a while:

zarrelli:~$ ./write-term.sh 
^Couch, we received a INT signal. Outta here but first a bit of cleaning

It seems it worked; let's have a look at the filesystem:

zarrelli:~$ ls -lh
ttotal 20K
-rw-r--r-- 1 zarrelli zarrelli 20 Apr 16 07:54 open
-rwxr--r-- 1 zarrelli zarrelli 193 Apr 16 13:27 test.sh
-rwxr--r-- 1 zarrelli zarrelli 35 Apr 16 11:54 while.sh
-rwxr-xr-x 1 zarrelli zarrelli 152 Apr 16 13:05 write.sh
-rwxr-xr-x 1 zarrelli zarrelli 293 Apr 16 13:28 write-term.sh

Clean, write.log has been cleaned upon exit. This is the expected and desired behavior. We can also go further, shielding the process from a signal so that is essentially ignored. Let's add the following line to our script:

trap ‘' TERM

Now, let's execute the script in the background:

zarrelli:~$ ./write-term.sh &
[1] 16831

Well, since we are going to deal with daemons, we do not have to fear killing innocent processes:

zarrelli:~$ kill 16831

Haha! We killed you!

zarrelli:~$ jobs
[1]+ Running ./write-term.sh &

Ahem, we have to reconsider our statement. It seems that our trap worked very well. In fact, a trap with a signal but with just ‘' as argument simply lets the signal be ignored. Well, we have other means of destruction, as we can invoke INT:

zarrelli:~$ kill -INT 16831
zarrelli:~$ ouch, we received a INT signal. Outta here but first a bit of cleaning

[1]+ Done ./write-term.sh

Finally, we exited the script in an orderly manner, no logs left behind:

zarrelli:~$ ls -lh
total 16K
-rw-r--r-- 1 zarrelli zarrelli 20 Apr 16 07:54 open
-rwxr--r-- 1 zarrelli zarrelli 35 Apr 16 11:54 while.sh
-rwxr-xr-x 1 zarrelli zarrelli 152 Apr 16 13:05 write.sh
-rwxr-xr-x 1 zarrelli zarrelli 307 Apr 16 13:39 write-term.sh

The filesystem is clean, no write.log left on it. Now, let's see a tricky use of a trap adding a few bits to our script. Let's start with y=0 placed at the very opening of the script, followed by a slightly revised loop:

for i in {1..3}
do
if (( x == 3 ))
then
y="$x"
echo "The value of x is: $x" >> write.log
fi
trap 'echo "The value of $y is "${y}""' DEBUG
done

Now, let's run the script:

zarrelli:~$ ./write-debug.sh 
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "0"
The value of $y is "3"
The value of $y is "3"
If a signal is received while Bash is waiting for a command to complete, the trap will be executed only after the command is over with its execution. If the built-in wait is used, it will return immediately upon receiving a signal for which a trap is set and the trap itself gets executed. Notice that a trap usually exits with a status of 0, but, in this case, the value of the exit status will be higher than 128.

Each time a command is executed, the value of the variable is printed as we can see, debugging the script with the -x option added to the Bash:

zarrelli:~$ /bin/bash -x ./write-debug.sh 
+ y=0
+ trap clean_exit INT
+ trap '' TERM
+ for i in {1..3}
+ x=1
+ (( x == 3 ))
+ trap 'echo "The value of $y is "${y}""' DEBUG
+ for i in {1..3}
++ echo 'The value of $y is "0"'
The value of $y is "0"
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ x=2
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ (( x == 3 ))
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ trap 'echo "The value of $y is "${y}""' DEBUG
+ for i in {1..3}
++ echo 'The value of $y is "0"'
The value of $y is "0"
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ x=3
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ (( x == 3 ))
++ echo 'The value of $y is "0"'
The value of $y is "0"
+ y=3
++ echo 'The value of $y is "3"'
The value of $y is "3"
+ echo 'The value of x is: 3'
++ echo 'The value of $y is "3"'
The value of $y is "3"
+ trap 'echo "The value of $y is "${y}""' DEBUG

Move around the trap line and see how much info you can gather by modifying it to suit your needs. So, play for a while and have fun preparing for the the final touch of magic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.196.59