send pid back to forker (accidentally removed)
[god.git] / lib / god / process.rb
blob5da262a2166a2360cd92a940e835c08336a38af7
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
10       self.log = '/dev/null'
11       
12       @pid_file = nil
13       @tracking_pid = true
14       @user_log = false
15     end
16     
17     def alive?
18       begin
19         pid = File.read(self.pid_file).strip.to_i
20         System::Process.new(pid).exists?
21       rescue Errno::ENOENT
22         false
23       end
24     end
25     
26     def file_writable?(file)
27       pid = fork do
28         ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid
29         ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid
30         
31         File.writable?(file) ? exit(0) : exit(1)
32       end
33       
34       wpid, status = ::Process.waitpid2(pid)
35       status.exitstatus == 0 ? true : false
36     end
37     
38     def valid?
39       # determine if we're tracking pid or not
40       self.pid_file
41       
42       valid = true
43       
44       # a start command must be specified
45       if self.start.nil?
46         valid = false
47         applog(self, :error, "No start command was specified")
48       end
49       
50       # self-daemonizing processes must specify a stop command
51       if !@tracking_pid && self.stop.nil?
52         valid = false
53         applog(self, :error, "No stop command was specified")
54       end
55       
56       # uid must exist if specified
57       if self.uid
58         begin
59           Etc.getpwnam(self.uid)
60         rescue ArgumentError
61           valid = false
62           applog(self, :error, "UID for '#{self.uid}' does not exist")
63         end
64       end
65       
66       # gid must exist if specified
67       if self.gid
68         begin
69           Etc.getgrnam(self.gid)
70         rescue ArgumentError
71           valid = false
72           applog(self, :error, "GID for '#{self.gid}' does not exist")
73         end
74       end
75       
76       # pid dir must exist if specified
77       if !@tracking_pid && !File.exist?(File.dirname(self.pid_file))
78         valid = false
79         applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist")
80       end
81       
82       # pid dir must be writable if specified
83       if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file))
84         valid = false
85         applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}")
86       end
87       
88       # log dir must exist
89       if !File.exist?(File.dirname(self.log))
90         valid = false
91         applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist")
92       end
93       
94       # log file or dir must be writable
95       if File.exist?(self.log)
96         unless file_writable?(self.log)
97           valid = false
98           applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}")
99         end
100       else
101         unless file_writable?(File.dirname(self.log))
102           valid = false
103           applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}")
104         end
105       end
106       
107       valid
108     end
109     
110     # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
111     # No really, trust me. Use the instance variable.
112     def pid_file=(value)
113       # if value is nil, do the right thing
114       if value
115         @tracking_pid = false
116       else
117         @tracking_pid = true
118       end
119       
120       @pid_file = value
121     end
122     
123     def pid_file
124       @pid_file ||= default_pid_file
125     end
126     
127     def start!
128       call_action(:start)
129     end
130     
131     def stop!
132       call_action(:stop)
133     end
134     
135     def restart!
136       call_action(:restart)
137     end
138     
139     def spawn(command)
140       fork do
141         ::Process.setsid
142         ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid
143         ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid
144         Dir.chdir "/"
145         $0 = command
146         STDIN.reopen "/dev/null"
147         STDOUT.reopen self.log, "a"
148         STDERR.reopen STDOUT
149         
150         # close any other file descriptors
151         3.upto(256){|fd| IO::new(fd).close rescue nil}
152         
153         exec command unless command.empty?
154       end
155     end
156     
157     def call_action(action)
158       command = send(action)
159       
160       if action == :stop && command.nil?
161         pid = File.read(self.pid_file).strip.to_i
162         name = self.name
163         command = lambda do
164           applog(self, :info, "#{self.name} stop: default lambda killer")
165           
166           ::Process.kill('HUP', pid) rescue nil
167           
168           # Poll to see if it's dead
169           5.times do
170             begin
171               ::Process.kill(0, pid)
172             rescue Errno::ESRCH
173               # It died. Good.
174               return
175             end
176             
177             sleep 1
178           end
179           
180           ::Process.kill('KILL', pid) rescue nil
181         end
182       end
183             
184       if command.kind_of?(String)
185         pid = nil
186         
187         if @tracking_pid
188           # double fork god-daemonized processes
189           # we don't want to wait for them to finish
190           r, w = IO.pipe
191           begin
192             opid = fork do
193               STDOUT.reopen(w)
194               r.close
195               pid = self.spawn(command)
196               puts pid.to_s # send pid back to forker
197             end
198             
199             ::Process.waitpid(opid, 0)
200             w.close
201             pid = r.gets.chomp
202           ensure
203             # make sure the file descriptors get closed no matter what
204             r.close rescue nil
205             w.close rescue nil
206           end
207         else
208           # single fork self-daemonizing processes
209           # we want to wait for them to finish
210           pid = self.spawn(command)
211           ::Process.waitpid(pid, 0)
212         end
213         
214         if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
215           File.open(default_pid_file, 'w') do |f|
216             f.write pid
217           end
218           
219           @tracking_pid = true
220           @pid_file = default_pid_file
221         end
222       elsif command.kind_of?(Proc)
223         # lambda command
224         command.call
225       else
226         raise NotImplementedError
227       end
228     end
229     
230     def default_pid_file
231       File.join(God.pid_file_directory, "#{self.name}.pid")
232     end
233   end