Thursday, December 11, 2008

A Bourne Again Daemon

In Unix-like systems, a daemon is a process that runs in the background, usually providing some kind of service. Most daemons can be identified in the process list because their names end in d. Examples are the Apache web server (httpd) and the OpenSSH server (sshd). Another thing daemons usually have in common is that they are written in the C programming language. To create a daemon quickly, you can throw one together using the Bourne Again Shell (Bash) scripting language.

note: originally published April 20, 2006 on linuxboxadmin.com
updated for OS X on August 15, 2007


More about daemons

There are a couple of other common daemon characteristics. They often have a configuration file with a name ending in ".conf". You can tell a daemon to read its configuration file again by sending it signal HUP (1). Lastly, daemons usually listen on a particular TCP or UDP port for incoming network connections. The network functionality is the only feature we won't duplicate using BASH. It is possible to add a network aware program to a BASH daemon, but that is left as an exercise for the reader.

Please allow me to introduce myself

Here is the basic structure of a BASH daemon:

#!/bin/bash
# This is a simple daemon shell script.
#
# Traps:
# signal HUP (1) read config file again
# signal QUIT (3) delete temp files
# signal TERM (15) delete temp files

conf=/tmp/bashdaemon.conf
source $conf
tempfile=/tmp/BASHDAEMON_$$
touch $tempfile

# set traps
trap 'source $conf' 1
trap 'rm -f $tempfile; exit' 0
trap 'rm -f $tempfile; exit' 3
trap 'rm -f $tempfile; exit' 15

# loop forever
while :
do
echo $foo
echo $bar
sleep 30
done
If you want to play along at home, save the script as bashdaemon.sh and make it executable with:
chmod a+x bashdaemon.sh

The bashdaemon.conf file contains variable assignments for two variables, foo and bar. It starts out like this:

foo=famine
bar=death

Let's walk through the script line by line.

conf=/tmp/bashdaemon.conf

This line defines the configuration file, called /tmp/bashdaemon.conf. In the example, all files are in the /tmp directory, but if you want to follow daemon convention, you could put it in /etc.

source $conf

The source command is a BASH built-in that reads and executes the configuration file, activating the variable assignments.

tempfile=/tmp/BASHDAEMON_$$
touch $tempfile


The tempfile serves two purposes. It can be used as work area and it stores the process ID of the daemon in the file name. For example, if the process ID was 1234, the tempfile name would be /tmp/BASHDAEMON_1234. As long as the daemon is running, the tempfile will exist. It gets automatically deleted when the daemon ends (unless it is killed with signal KILL (9)).

trap 'source $conf' 1

This trap allows the daemon to capture signal HUP (1) and tells it to source the configuration file again and continue running. If the variable assignments have been changed, the new values take effect.

trap 'rm -f $tempfile; exit' 0
trap 'rm -f $tempfile; exit' 3
trap 'rm -f $tempfile; exit' 15

These lines trap signals 0, 3, and 15 to delete the tempfile, then exit. See below for more about traps.

while :
do
echo $foo
echo $bar
sleep 30
done

The loop uses a never ending while statement to run until it is ended with one of the signals that tell it to exit or signal 9, killing it unconditionally. Inside the loop, the daemon does its work. In the example, it just prints the contents of the foo and bar variables, then goes to sleep for 30 seconds.

Traps

For those not familiar with traps, a few more words are in order. If you already know about traps, skip ahead. Linux (and Unix) defines a set of messages called signals that can be sent to a program to tell it what to do. The "kill" program is used to send signals from the command line. To view the complete list of signals, try:

kill -l

That is an L, not the numeral one. For example, the hang up signal (HUP) is signal 1 and the QUIT signal is signal 3. When a program receives the QUIT signal, it exits immediately, unless the program is written to recognize that signal and take other action. Intercepting a signal is called a trap. Most languages have a trap function and in BASH, it is the trap built-in function. The one signal that can't be trapped is KILL -- signal 9. The man page has more details.

Firing it up

There are many ways to run a Bash daemon, depending on how formal you want to get. The least formal way is to start it from a terminal session with the background operator:

./bashdaemon.sh &

This starts the daemon in the background and works, but requires the terminal session to remain open. Another option is to start it from a screen session. Screen is a program that lets you detach a process from the terminal and reattach to it later, even from a different computer. If you haven't tried screen before, it is a powerful tool worth learning. After launching the daemon from screen, you can simply detach, close the terminal, and check on it later as needed.

The most formal option in OS X is to set up a property list file like the big daemons use and have launchd manage it. Linux users could set up a run control script in /etc/rc.d/init.d/. On the other hand, if you need to go to these lengths, you probably need to write it in a more appropriate language. BASH daemons excel at quick and dirty solutions.

Killing it softly

To test the signal HUP trap, I started the BASH daemon in the background, then changed the variable assignments in the bashdaemon.conf file. After my edits, the bashdaemon.conf file looked like this:

foo=pestilence
bar=war

Next, I found the process ID of the daemon (1927) by looking at the tempfile name in /tmp. Then, I sent signal 1 to it using:

kill -s HUP 1927

After it received the signal, it started printing the new values from bashdaemon.conf. To kill it for good, I pulled the daemon back into the foreground with code>fg and used Ctrl-C to end it. Afterward, the tempfile was gone as expected.

Daemons for work and play

Some interesting near real-time stuff can be done with BASH daemons. You could monitor a database, be notified when a certain user logs in, take action when a file is accessed, when a program is run, or anything you dream up. Summon your own daemons to do your bidding.