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