default stop for auto-daemonized; bug fixes
[god.git] / lib / god / process.rb
bloba4c34ed84b21ea20b374aaf9d564839f825d03a2
1 require 'fileutils'
3 module God
4   class Process
5     WRITES_PID = [:start, :restart]
6     
7     attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart
8     
9     def initialize(options={})
10       options.each do |k,v|
11         send("#{k}=", v)
12       end
13       
14       @tracking_pid = false
15     end
16     
17     def valid?
18       # determine if we're tracking pid or not
19       self.pid_file
20       
21       valid = true
22       
23       # a name must be specified
24       if self.name.nil?
25         valid = false
26         puts "No name was specified"
27       end
28       
29       # a start command must be specified
30       if self.start.nil?
31         valid = false
32         puts "No start command was specified"
33       end
34       
35       # self-daemonizing processes must specify a stop command
36       if !@tracking_pid && self.stop.nil?
37         valid = false
38         puts "No stop command was specified"
39       end
40       
41       # self-daemonizing processes cannot specify log
42       if !@tracking_pid && self.log
43         valid = false
44         puts "Self-daemonizing processes cannot specify a log file"
45       end
46       
47       # uid must exist if specified
48       if self.uid
49         begin
50           Etc.getpwnam(self.uid)
51         rescue ArgumentError
52           valid = false
53           puts "UID for '#{self.uid}' does not exist"
54         end
55       end
56       
57       # gid must exist if specified
58       if self.gid
59         begin
60           Etc.getgrnam(self.gid)
61         rescue ArgumentError
62           valid = false
63           puts "GID for '#{self.gid}' does not exist"
64         end
65       end
66       
67       valid
68     end
69     
70     # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
71     # No really, trust me. Use the instance variable.
72     def pid_file=(value)
73       @tracking_pid = false
74       @pid_file = value
75     end
76     
77     def pid_file
78       if @pid_file.nil?
79         @tracking_pid = true
80         @pid_file = default_pid_file
81       end
82       @pid_file
83     end
84     
85     def start!
86       call_action(:start)
87     end
88     
89     def stop!
90       call_action(:stop)
91     end
92     
93     def restart!
94       call_action(:restart)
95     end
96     
97     def call_action(action)
98       command = send(action)
99       
100       if action == :stop && command.nil?
101         # command = "kill -9 `cat #{self.pid_file}`"
102         pid = File.read(self.pid_file).strip.to_i
103         name = self.name
104         # log_file = self.log
105         command = lambda do
106           # File.open(log_file, 'a') do |logger|
107           #   logger.puts "god stop [" + Time.now.strftime("%Y-%m-%d %H:%M:%S") + "] lambda killer"
108           #   logger.flush
109             
110             puts "#{self.name} stop: default lambda killer"
111             
112             ::Process.kill('HUP', pid) rescue nil
114             # Poll to see if it's dead
115             5.times do
116               begin
117                 ::Process.kill(0, pid)
118               rescue Errno::ESRCH
119                 # It died. Good.
120                 return
121               end
123               sleep 1
124             end
126             ::Process.kill('KILL', pid) rescue nil
127           # end
128         end
129       end
130             
131       if command.kind_of?(String)
132         # Make pid directory
133         unless test(?d, God.pid_file_directory)
134           begin
135             FileUtils.mkdir_p(God.pid_file_directory)
136           rescue Errno::EACCES => e
137             abort "Failed to create pid file directory: #{e.message}"
138           end
139         end
140         
141         unless test(?w, God.pid_file_directory)
142           abort "The pid file directory (#{God.pid_file_directory}) is not writable by #{Etc.getlogin}"
143         end
144         
145         # string command
146         # fork/exec to setuid/gid
147         r, w = IO.pipe
148         opid = fork do
149           STDOUT.reopen(w)
150           r.close
151           pid = fork do
152             ::Process.setsid
153             ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid
154             ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid
155             Dir.chdir "/"
156             $0 = command
157             STDIN.reopen "/dev/null"
158             if self.log
159               STDOUT.reopen self.log, "a"
160             else
161               STDOUT.reopen "/dev/null", "a"
162             end
163             STDERR.reopen STDOUT
164             
165             # STDOUT.puts "god #{action} [" + Time.now.strftime("%Y-%m-%d %H:%M:%S") + "] " + command
166             # STDOUT.flush
167             
168             exec command unless command.empty?
169           end
170           puts pid.to_s
171         end
172         
173         ::Process.waitpid(opid, 0)
174         w.close
175         pid = r.gets.chomp
176         
177         if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
178           File.open(default_pid_file, 'w') do |f|
179             f.write pid
180           end
181           
182           @tracking_pid = true
183           @pid_file = default_pid_file
184         end
185         
186       elsif command.kind_of?(Proc)
187         # lambda command
188         command.call
189       else
190         raise NotImplementedError
191       end
192     end
193     
194     def default_pid_file
195       File.join(God.pid_file_directory, "#{self.name}.pid")
196     end
197   end