Just some background before we get started. I do most of my work on openSUSE, but I’m sure most of the work will be usefull. You’ll also notice that I do a lot of error checking. I don’t write code that just dies on errors, its a bit more work but worth it in the long run when it comes to debugging and stability. The following was developed in openSUSE 10.3
The Perl Code:
The first thing we’ll need is:
use POSIX qw(setsid);
setsid() is a POSIX function that turns the process into a session leader, group leader, and ensures that it doesn’t have a controlling terminal. We will use this function call later.
You will also need to change the working directory to / using chdir(‘/’)
Setting umask to 0 will remove any default permissions allowing us to create files as we see fit.
Finally you will need to fork and exit the parent program leaving only its child to survive independant from its parent process.
As the child (the actual daemon) you need to setsid() and read our new pid for processing later.
We can do all the ‘daemonizing’ work in a single sub.
The code looks like this:
sub Daemonize {
unless (chdir ‘/’) {
$error .= “Can’t chdir to /: $!”;
}
unless (umask 0) {
$error .= “Unable to umask 0”;
}defined(my $pid = fork);
#Exit if $pid exists (parent)
exit if $pid;#as child
setsid;
$proc = $$;
return ($proc);
}
Something else you may want to do is change the STDIN,STDOUT and STDERR to our own log file. Here is a quick example of how to do this:
unless (open STDIN, ‘/dev/null’) {
$error .= “Can’t read /dev/null: $!”;
}
unless (open STDOUT, ‘>>/var/log/daemon.log’) {
$error .= “Can’t read /var/log/daemon.log: $!”;
}
unless (open STDERR, ‘>>/var/log/daemon.log’) {
$error .= “Can’t write to /var/log/daemon.log: $!”;
}
Now anything we print and any errors will go to our log file.
To keep from having multiple instances of your daemon there are a number of techniques.
The one I found works well for me is to just write the new PID, return($proc), to a file in /var/run
Doing it this way will let us use Killproc in our init script, which will make things much easier.
There are some obvious issues with this method. It is possible the process could die, leaving a zombie pid behind. Startup will never succeed. I haven’t though too much into this at this time. But I’m certain a quick validation of the pid value could free us from this. I will add in comments where you can perform this check.
The code will be:
if (!$error) {
if (-e $pid2check) {
LogMessage(“$file : PID File $pid2check already exists. Exiting”);
#Here is where you could validate the contents of the pidfile
exit(0);} else {
unless (open (FILE, $pidfile)) {
$error .= “Error opening pid file $pidfile for writing ” . $!;
}
}
}
if (!$error) {
LogMessage(“$file : PID $proc : Writing pid information to $pidfile”);
print FILE $proc . “n”;
close (FILE);
}
LogMessage() is a function that will log our messages to the new STDOUT using a print statement.
The main body of the daemon will be held in a single while loop.
while (!$error) {
sleep(1);
LogMessage(“Hello World”);
}
Inside the while loop you can do what ever you need.
So the whole script would look like this:
#!/usr/bin/perl
#################################################################
#
# daemon.pl
# Programmer: Shawn Holland
# I am not responsible for anything.
#
#################################################################
use POSIX qw(setsid);my $proc;
my $error;
my $file = “daemon.pl”;
my $pidfile = “>/var/run/daemon.pid”;
my $pid2check = “/var/run/daemon.pid”;
my $pid;#Make it a daemon
$proc = Daemonize();if (!$error) {
LogMessage(“$file : PID $proc : Begin”);
}#Write Pid Information
if (!$error) {
if (-e $pid2check) {
LogMessage(“$file : PID File $pid2check already exists. Exiting”);
exit(0);
} else {
unless (open (FILE, $pidfile)) {
$error .= “Error opening file for writing ” . $!;
}
}
}
if (!$error) {
LogMessage(“$file : PID $proc : Writing pid information to $pidfile”);
print FILE $proc . “n”;
close (FILE);
}#Main loop of Daemon
while (!$error) {
sleep(1);
LogMessage(“Hello World”);
}if ($error) {
LogMessage(“$file : PID $proc : Error $error”);
}LogMessage(“$file : PID $proc : END”);
exit(0);
#
#Subs
#
#################################################################
#
# Daemonize
#
#################################################################
#
# Used to make this program a daemon
# Also to redirect STDIN, STDERR, STDOUT
# Returns PID
#
#################################################################
sub Daemonize {unless (chdir ‘/’) {
$error .= “Can’t chdir to /: $!”;
}
unless (umask 0) {
$error .= “Unable to umask 0”;
}unless (open STDIN, ‘/dev/null’) {
$error .= “Can’t read /dev/null: $!”;
}#All print statments will now be sent to our log file
unless (open STDOUT, ‘>>/var/log/daemon.log’) {
$error .= “Can’t read /var/log/daemon.log: $!”;
}
#All error messages will now be sent to our log file
unless (open STDERR, ‘>>/var/log/daemon.log’) {
$error .= “Can’t write to /var/log/daemon.log: $!”;
}defined($pid = fork);
#Exit if $pid exists (parent)
exit(0) if $pid;#As Child
setsid();
$proc = $$;
return ($proc);
}#################################################################
#
# LogMessage
#
#################################################################
#
# Used to log messages
#
#################################################################
sub LogMessage {
my $message = $_[0];
print localtime() . ” $messagen”;
}
A quick test to make sure everything is working
shawn:~ # perl daemon.pl.source
shawn:~ # cat /var/log/daemon.log
Fri May 15 00:23:30 2009 daemon.pl : PID 22702 : Begin
Fri May 15 00:23:30 2009 daemon.pl : PID 22702 : Writing pid information to >/var/run/daemon.pid
Fri May 15 00:23:31 2009 Hello World
Fri May 15 00:23:32 2009 Hello World
Fri May 15 00:23:33 2009 Hello World
Fri May 15 00:23:34 2009 Hello World
Fri May 15 00:23:35 2009 Hello Worldshawn:~ # cat /var/run/daemon.pid
22702
and ps will show our new daemon running
shawn:~ # ps u -p 22702
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
shawn 22702 0.0 0.1 5296 1336 ? Ss 00:23 0:00 perl daemon.pl.source
Compile:
To compile this new daemon there are a few perl compilers out there. Some commercial and some opensource. Always short on cash and big fan of FOSS I use pp – PAR Packager
You will need to install PAR::Packer from cpan. You should be able to do this with the command
cpan PAR::Packer
All the defaults “should” be fine.
Once installed you can compile your daemon with the following:
pp -o ./daemon.pl -M POSIX.pm ./daemon.pl.source
You will most likely need to add in specific perl modules.
For example if you are using DBI to work with a MySQL database.
pp -o ./daemon.pl -M POSIX.pm -M DBI.pm ./daemon.pl.source
Now you should have a compiled Perl daemon.