better handling of DRb unix domain socket
[god.git] / lib / god / process.rb
bloba9c2c07d4e4997e3719c2fa09a371f5256b731ab
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_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           opid = fork do
192             STDOUT.reopen(w)
193             r.close
194             pid = self.spawn(command)
195             puts pid.to_s
196           end
197           
198           ::Process.waitpid(opid, 0)
199           w.close
200           pid = r.gets.chomp
201         else
202           # single fork self-daemonizing processes
203           # we want to wait for them to finish
204           pid = self.spawn(command)
205           ::Process.waitpid(pid, 0)
206         end
207         
208         if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
209           File.open(default_pid_file, 'w') do |f|
210             f.write pid
211           end
212           
213           @tracking_pid = true
214           @pid_file = default_pid_file
215         end
216       elsif command.kind_of?(Proc)
217         # lambda command
218         command.call
219       else
220         raise NotImplementedError
221       end
222     end
223     
224     def default_pid_file
225       File.join(God.pid_file_directory, "#{self.name}.pid")
226     end
227   end