Saturday, June 28, 2014

A Perl daemon

A daemon is a process running in the background continuously. It is the linux comparable of a windows service. A daemon or windows service can do many things in the background. If you let it open and listen to a port, it becomes a server, e.g., a web server like Apache or IIS, or a database server like MySQL or MongoDB. For this reason, it is a very interesting and useful thing to learn how to write a daemon or windows service. The installation and/or execution of a linux daemon or windows service usually requires the user to have admin permission.

A Perl daemon can be easily implemented using the Proc::Daemon module [1]. If you don't have this installed, you can do it by:

sudo perl -MCPAN -e shell
cpan[1]> install Proc::Daemon
cpan[2]> install Proc::PID::File

A Perl daemon can be as simple as this (note you will need to run it with "sudo"):

#!/usr/bin/perl
use Proc::Daemon;
use Proc::PID::File;

Proc::Daemon::Init; # Demonize.
if (Proc::PID::File->running()) { exit(0); # Exit if already running, so only 1 instance can run.
for (;;) { 
    # print "do something every 5 seconds..\n"; # This won't print because STDOUT is closed.
    sleep(5); 
}


You cannot really see any console print output since STDOUT is closed by Proc::Daemon. You can see output by printing to a log file, or use the "top" command to see this new process.

Here is a more full-fledged version of a Perl daemon. It allows you to use start/stop/status commands to control and monitor the daemon. A log file is also generated.


#
# This script demonstates:
# 1) running a Perl script as daemon in background, using the Daemon module.
# 2) only one instance of the daemon can run by checking "Proc::PID::File->running()".
# 3) implementation of daemon commands: start, stop, status.
# 4) use of Getopt::Long, $0 (name of this file).
#
# Note:
# 1) to run as daemon, "sudo" should be used for non-admin user.
# 2) parameters that can change: $LOG_FILE, $USE_OPT.
#
# @By: X.C.
# @Created on: 6/28/2014
# @Last modified: 6/28/2014
#

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use Proc::Daemon;
use Proc::PID::File;

#
# Location of log file.
#
my $LOG_FILE = "/Users/chenx/tmp/dmon.log";

#
# If $USE_OPT = 1, use GetOptions.
# 0 is better here because if an arg does not start with "--",
# it will be ignored and no usage information is printed.
#
my $USE_OPT = 0;


my $len = @ARGV;
if ($len == 0) {
    show_usage();
} else {
    if ($USE_OPT) {
        GetOptions(
            "start" => \&do_start,
            "status" => \&show_status,
            "stop" => \&do_stop,
            "help" => \&show_usage
        ) or show_usage();
    } else {
        my $cmd = $ARGV[0];
        if ($cmd eq "start") { do_start(); }
        elsif ($cmd eq "stop") { do_stop(); }
        elsif ($cmd eq "status") { show_status(); }
        else { show_usage(); }
    }
}


#
# 1 at the end of a module means that the module returns true to use/require statements.
# It can be used to tell if module initialization is successful.
# Otherwise, use/require will fail.
#
# 1;


sub show_usage {
    if ($USE_OPT) {
        print "Usage: sudo perl $0 --[start|stop|status|help]\n";
    } else {
        print "Usage: sudo $0 [start|stop|status]\n";
    }
    exit(0);
}


sub show_status {
    if (Proc::PID::File->running()) {
        print "daemon is running..\n";
    } else {
        print "daemon is stopped\n";
    }
}


sub do_stop {
    my $pid = Proc::PID::File->running();
    if ($pid == 0) {
        print "daemon is not running\n";
    } else {
        #print "stop daemon now ..\n";
        kill(9, $pid);
        print "daemon is stopped\n";
    }
}


sub do_start {
    print "start daemon now\n";

    Proc::Daemon::Init();

    #
    # To use this, you need to start the daemon with "sudo" to have the permission
    # to PID file. To kill the daemon, "sudo" is also needed.
    #
    if (Proc::PID::File->running()) {
        do_log( "A copy of this daemon is already running, exit" );
        exit(0);
    }

    my $continue = 1;
    $SIG{TERM} = sub { $continue = 0; };

    while ($continue) {
        #print "continue ..\n"; # this won't work, since STDOUT is closed.
        do_log("continuing ..");

        sleep(2);
    }
}


sub do_log {
    my ($msg) = @_;

    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    $year += 1900;

    open FILE, ">>$LOG_FILE" or die "cannot open log file $!\n";
    print FILE "$year-$mon-$mday $hour:$min:$sec  $msg\n";
    close FILE;
}


Alternatively, you can also implement a daemon without using Proc::Daemon.  An example is below.

This code [2] is back from year 2000. The current Proc::Daemon implements majority of these ideas. These are also the basic requirements of a well-rounded daemon, so it won't become a demon:

1) Fork a child process as the daemon, the exit the parent process. This guarantees the daemon process is free of any controlling process and terminal.
2) Call setsid. This makes the daemon process a session leader, and free of any controlling terminal/shell.
3) Change working dir, usually "/". This prevents the relevant partition where the working dir resides from resisting unmounting by admin.
4) Use umask to cancel the default file creation mode permission inherited from parent process.
5) Close unneeded file handlers, mostly STDIN, STDOUT and STDERR, by redirecting them to /dev/null. This is because a daemon has no associated terminal/shell and has nowhere to write these to.
6) Logging message, so you know what's going on when STDOUT and STDERR are closed.


#!/usr/bin/perl

#
# This demonstrates running a Perl program in background as daemon without using Proc::Daemon.
# More than 1 instance can run at the same time.
# From: [2] 
#

use POSIX qw(setsid);

chdir '/';
umask 0;
open STDIN, '/dev/null';
#open STDOUT, '>/Users/chenx/tmp/dmon.log';
open STDERR, '>/dev/null';

defined(my $pid = fork);
exit if $pid;
setsid;

while(1)
{
    sleep(2);
    #print "Hello...\n";
    do_log();
}

# note: cannot use "log" as it's preserved.
sub do_log {
    open LOGFILE, '>>', '/Users/chenx/tmp/dmon.log' or die "cannot open file"; # $!;
    print LOGFILE "continue on ..\n";
    close LOGFILE;
}


There is an interesting Perl Daemon Contest [3] here, back in 2000. Some of these are pretty complicated and highly practical, such as web proxy, system monitoring and reporting, stock monitoring, even a reversed-engineered printer daemon, and an interesting AI game of rock/scissors/paper played inside the file system.


References:

[1] Proc::Daemon implementation version 0.14
[2] Unix Daemon In Perl Tutorial
[3] Perl Daemon Contest


No comments:

Blog Archive

Followers