# Copyright (c) 2007, 2009, 2011 Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'fileutils' require 'rexec/daemon/controller' require 'rexec/reverse_io' module RExec module Daemon # This class is the base daemon class. If you are writing a daemon, you should inherit from this class. # # The basic structure of a daemon is as follows: # # class Server < RExec::Daemon::Base # def self.run # # Long running process, e.g. web server, game server, etc. # end # end # # Server.daemonize # # The base directory specifies a path such that: # working_directory = #{@@base_directory}/#{daemon_name} # log_directory = #{working_directory}/log # log_file_path = #{log_directory}/daemon.log # runtime_directory = #{working_directory}/run # process_file_path = #{runtime_directory}/daemon.pid class Base # For a system-level daemon you might want to specify "/var" @@base_directory = "." # Return the name of the daemon def self.daemon_name return name.gsub(/[^a-zA-Z0-9]+/, '-') end # The directory the daemon will run in. def self.working_directory @@base_directory end # Return the directory to store log files in. def self.log_directory File.join(working_directory, "log") end # Standard log file for stdout and stderr. def self.log_file_path File.join(log_directory, "#{daemon_name}.log") end # Runtime data directory for the daemon. def self.runtime_directory File.join(working_directory, "run") end # Standard location of process pid file. def self.process_file_path File.join(runtime_directory, "#{daemon_name}.pid") end # Mark the output log. def self.mark_log File.open(log_file_path, "a") do |log_file| log_file.puts "=== Log Marked @ #{Time.now.to_s} ===" end end # Prints some information relating to daemon startup problems. def self.tail_log(output) lines = [] File.open(log_file_path, "r") do |log_file| log_file.seek_end log_file.reverse_each_line do |line| lines << line break if line.match("=== Log Marked") || line.match("=== Daemon Exception Backtrace") end end lines.reverse_each do |line| output.puts line end end # Check the last few lines of the log file to find out if the daemon crashed. def self.crashed? File.open(log_file_path, "r") do |log_file| log_file.seek_end count = 3 log_file.reverse_each_line do |line| return true if line.match("=== Daemon Crashed") count -= 1 break if count == 0 end end return false end # Corresponds to controller method of the same name def self.daemonize Controller.daemonize(self) end # Corresponds to controller method of the same name def self.start Controller.start(self) end # Corresponds to controller method of the same name def self.stop Controller.stop(self) end # Corresponds to controller method of the same name def self.status Controller.status(self) end # The main function to setup any environment required by the daemon def self.prefork @@base_directory = File.expand_path(@@base_directory) if @@base_directory FileUtils.mkdir_p(log_directory) FileUtils.mkdir_p(runtime_directory) end # The main function to start the daemon def self.run end # The main function to stop the daemon def self.shutdown # Interrupt all children processes, preferably to stop them so that they are not left behind. Process.kill(0, :INT) end end end end