remove gods pid file on user requested termination
[god.git] / lib / god / process.rb
blobc74f020dcd0b74132cb89125e3789a1cc4ad6b94
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         exec command unless command.empty?
151       end
152     end
153     
154     def call_action(action)
155       command = send(action)
156       
157       if action == :stop && command.nil?
158         pid = File.read(self.pid_file).strip.to_i
159         name = self.name
160         command = lambda do
161           applog(self, :info, "#{self.name} stop: default lambda killer")
162           
163           ::Process.kill('HUP', pid) rescue nil
164           
165           # Poll to see if it's dead
166           5.times do
167             begin
168               ::Process.kill(0, pid)
169             rescue Errno::ESRCH
170               # It died. Good.
171               return
172             end
173             
174             sleep 1
175           end
176           
177           ::Process.kill('KILL', pid) rescue nil
178         end
179       end
180             
181       if command.kind_of?(String)
182         pid = nil
183         
184         if @tracking_pid
185           # double fork god-daemonized processes
186           # we don't want to wait for them to finish
187           r, w = IO.pipe
188           opid = fork do
189             STDOUT.reopen(w)
190             r.close
191             pid = self.spawn(command)
192             puts pid.to_s
193           end
194           
195           ::Process.waitpid(opid, 0)
196           w.close
197           pid = r.gets.chomp
198         else
199           # single fork self-daemonizing processes
200           # we want to wait for them to finish
201           pid = self.spawn(command)
202           ::Process.waitpid(pid, 0)
203         end
204         
205         if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
206           File.open(default_pid_file, 'w') do |f|
207             f.write pid
208           end
209           
210           @tracking_pid = true
211           @pid_file = default_pid_file
212         end
213       elsif command.kind_of?(Proc)
214         # lambda command
215         command.call
216       else
217         raise NotImplementedError
218       end
219     end
220     
221     def default_pid_file
222       File.join(God.pid_file_directory, "#{self.name}.pid")
223     end
224   end