ry / ebb fork watch download tarball
public
Description: web server
Homepage: http://ebb.rubyforge.org
Clone URL: git://github.com/ry/ebb.git
Ryan Dahl (author)
Sun Feb 24 17:46:11 -0800 2008
commit  5b6d59ac6933821590e887157931a712a88e95c4
tree    b8524885e855add8e5223e22b348d3803d4895e1
parent  1fce3d5204583fde37459771e75d65a5f926db26
ebb / ruby_lib / daemonizable.rb
100644 126 lines (109 sloc) 3.863 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
# 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 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