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