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)
Thu Feb 28 01:52:12 -0800 2008
commit  9d780efaa4771ddf4f3fda30f7353188aaafa0e3
tree    5f01bf85dec5cc2bf25bf16a78daa6d734d502a4
parent  6f1a46c3f1ebc9b6f8b73e8c053a8047d058dc8d
ebb / ruby_lib / daemonizable.rb
100644 135 lines (117 sloc) 4.116 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
135
# 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