Perl : Creating a compiled daemon

865

 

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 World

shawn:~ # 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.