Writing very simple daemon in Ruby 1

Posted by Dan Sosedoff on January 24, 2009

Ruby is very powerful language, not only for web development as many can think for the first time. It is also providing all necessary resources to build system utilities and daemons. So, this post exactly about it.
Long time ago i was looking for some tool to write simple daemon in a short term. I didn`t choose C just because i was able to develop program only on special computer (i mean “production-like” environment) and the problem wasnt critical to resources. In other words – this daemon was working only few days to complete the task. Ok, lets see what we`ve got:

#!/usr/bin/ruby
# ---------------------------------------------------------------------
# MODULES
# ---------------------------------------------------------------------
require 'daemonize'
include Daemonize
include Process
# ---------------------------------------------------------------------
# CONFIGURATION
# ---------------------------------------------------------------------
$daemon = {
  :name => "Test Daemon",                  # daemon name
  :abbr => "testd",                        # daemon abbreviation
  :author => "(c) 2008 author",            # daemon author
  :version => "0.1",                       # actual version
  :file_log => "/var/log/testdaemon.log",  # log path
  :file_pid => "/var/run/testdaemon.pid",  # process id path
  :delay_sleep => 1,                       # seconds
  :user => 'tux',                          # working data user
  :grp => 'tux',                           # working data group
  :background => false,                    # background mode
  :work => true                            # daemon work flag
}
 
$daemon_log = nil
$daemon_pid = nil
 
# ---------------------------------------------------------------------
 
def daemon_log(str)
  puts "[#{Time.now.strftime("%m/%d/%Y-%H:%M:%S")}] #{str}"
end
 
def daemon_terminate
  $daemon[:work] = false
end
 
def daemon_stop
  daemon_log("Stopping working process...")
  $daemon_pid.close
  File.delete($daemon[:file_pid])
end
 
def daemon_start
  if File.exist?($daemon[:file_pid]) then
    daemon_log("Process already running. If it`s not - remove the pid file")
    exit
  end
 
  daemon_log("Starting process...")
  daemonize if $daemon[:background]
 
  begin
    $daemon_pid = File.new($daemon[:file_pid],"w")
  rescue Errno::EACCES
    daemon_log("Cannot create PID file. Check the permissions and try again!")
    $daemon_pid = nil
    exit
  end  
 
  daemon_work
end
 
def daemon_work
  if ($daemon_pid) then
    $daemon_pid.sync = true
    $daemon_pid.puts(Process.pid.to_s)
 
    begin
      while $daemon[:work] do
        daemon_log("Daemon working")
        daemon_handle_signals
        sleep($daemon[:delay_sleep])
      end
    rescue Exception => e
      daemon_log("Error: #{e.message}")
    end
 
    daemon_stop
  end
end
 
def daemon_handle_signals
  # termination signal
  Signal.trap("TERM") do
    daemon_log("TERM signal received.")
    daemon_terminate
  end  
 
  # kill signal
  Signal.trap("KILL") do
    daemon_log("KILL signal received.")
    daemon_terminate
  end
 
  # keyboard interruption
  Signal.trap("INT") do
    daemon_log("SIGINT signal received.")
    daemon_terminate
  end
 
  Signal.trap("TSTP") do
    daemon_log("SIGTSTP signal received.")
  end
end
 
def daemon_show_version
  puts "#{$daemon[:name]} v#{$daemon[:version]} #{$daemon[:author]}"
end
 
def daemon_show_usage
  daemon_show_version
  puts "Usage:"
  puts "    -b, --background        work in background mode"
  puts "    -v, --version           view version of daemon"
  puts "    -h, --help              view this help"
end
 
def daemon_parse_opts
  return true if ARGV.length == 0
 
  case ARGV[0]
    when '-b', '--background'
      $daemon[:background] = true;
      return true
 
    when '-v', '--version'
      daemon_show_version
 
    when '-h', '--help'
      daemon_show_usage
    else
      puts "Invalid argument: #{ARGV[0]}" if !ARGV[0].nil?
      daemon_show_usage
  end
 
  return false
end
 
def daemon_main
  daemon_start if daemon_parse_opts
end
 
daemon_main

This is just a basic structure of daemon, it supports background mode. I removed all unimportant information and left only main program cycles. As you can see, the entry point of whole program – daemon_main procedure. It parsing the command line parameters. In this example there is no required parameters, so daemon will run in basic mode (not background). To enable background you should specify the -d (or –background) option. Also, very important – this example needs to be executed under root or other user that have access to /var/run. Other way, PID file path can be changed to whatever you want (file_pid key). All daemon configuration variables stored in array $daemon. Also, it supports system signals handling, like SIGTERM or SIGKILL (all ruby signal constants u can explore here).

I think that`s it. Not a lot, but very simple. Download script: http://files.sosedoff.com/b57415aa/