File reorganization
[ebb.git] / ruby_lib / daemonizable.rb
blob86bc8d6124c823044460ff0f4edd94094e29b2b1
1 # Copyright (c) 2007 Marc-AndrĂ© Cournoyer
2 require 'etc'
4 module Kernel
5   unless respond_to? :daemonize # Already part of Ruby 1.9, yeah!
6     # Turns the current script into a daemon process that detaches from the console.
7     # It can be shut down with a TERM signal. Taken from ActiveSupport.
8     def daemonize
9       exit if fork                   # Parent exits, child continues.
10       Process.setsid                 # Become session leader.
11       exit if fork                   # Zap session leader. See [1].
12       Dir.chdir "/"                  # Release old working directory.
13       File.umask 0000                # Ensure sensible umask. Adjust as needed.
14       STDIN.reopen "/dev/null"       # Free file descriptors and
15       STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
16       STDERR.reopen STDOUT           # STDOUT/ERR should better go to a logfile.
17       trap("TERM") { exit }
18     end
19   end
20 end
22 module Process
23   # Returns +true+ the process identied by +pid+ is running.
24   def running?(pid)
25     Process.getpgid(pid) != -1
26   rescue Errno::ESRCH
27     false
28   end
29   module_function :running?
30 end
32 # Module included in classes that can be turned into a daemon.
33 # Handle stuff like:
34 # * storing the PID in a file
35 # * redirecting output to the log file
36 # * changing processs privileges
37 # * killing the process gracefully
38 module Daemonizable
39   attr_accessor :pid_file, :log_file
40   
41   def self.included(base)
42     base.extend ClassMethods
43   end
44   
45   def pid
46     File.exist?(pid_file) ? open(pid_file).read : nil
47   end
48   
49   # Turns the current script into a daemon process that detaches from the console.
50   def daemonize
51     raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
52     
53     pwd = Dir.pwd # Current directory is changed during daemonization, so store it
54     super         # Calls Kernel#daemonize
55     Dir.chdir pwd
56     
57     trap('HUP', 'IGNORE') # Don't die upon logout
59     # Redirect output to the logfile
60     [STDOUT, STDERR].each { |f| f.reopen @log_file, 'a' } if @log_file
61     
62     write_pid_file
63     at_exit do
64       log ">> Exiting!"
65       remove_pid_file
66     end
67   end
68   
69   # Change privileges of the process
70   # to the specified user and group.
71   def change_privilege(user, group=user)
72     log ">> Changing process privilege to #{user}:#{group}"
73     
74     uid, gid = Process.euid, Process.egid
75     target_uid = Etc.getpwnam(user).uid
76     target_gid = Etc.getgrnam(group).gid
78     if uid != target_uid || gid != target_gid
79       # Change process ownership
80       Process.initgroups(user, target_gid)
81       Process::GID.change_privilege(target_gid)
82       Process::UID.change_privilege(target_uid)
83     end
84   rescue Errno::EPERM => e
85     log "Couldn't change user and group to #{user}:#{group}: #{e}"
86   end
87   
88   module ClassMethods
89     # Kill the process which PID is stored in +pid_file+.
90     def kill(pid_file, timeout=60)
91       if pid = open(pid_file).read
92         pid = pid.to_i
93         print "Sending INT signal to process #{pid} ... "
94         begin
95           Process.kill('INT', pid)
96           Timeout.timeout(timeout) do
97             sleep 0.1 while Process.running?(pid)
98           end
99         rescue Timeout::Error
100           print "timeout, Sending KILL signal ... "
101           Process.kill('KILL', pid)
102         end
103         puts "stopped!"
104       else
105         puts "Can't stop process, no PID found in #{@pid_file}"
106       end
107     rescue Errno::ESRCH # No such process
108       puts "process not found!"
109     ensure
110       File.delete(pid_file) rescue nil
111     end
112   end
113   
114   private
115     def remove_pid_file
116       File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
117     end
119     def write_pid_file
120       log ">> Writing PID to #{@pid_file}"
121       FileUtils.mkdir_p File.dirname(@pid_file)
122       open(@pid_file,"w") { |f| f.write(Process.pid) }
123       File.chmod(0644, @pid_file)
124     end