fix process#alive? to not raise on no such file
[god.git] / lib / god / process.rb
blob224d66bca2d83985b8764cbfa1626664c52438dd
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       @pid_file = nil
11       @tracking_pid = false
12     end
13     
14     def alive?
15       begin
16         pid = File.read(self.pid_file).strip.to_i
17         System::Process.new(pid).exists?
18       rescue Errno::ENOENT
19         false
20       end
21     end
22     
23     def valid?
24       # determine if we're tracking pid or not
25       self.pid_file
26       
27       valid = true
28       
29       # a name must be specified
30       if self.name.nil?
31         valid = false
32         LOG.log(self, :error, "No name was specified")
33       end
34       
35       # a start command must be specified
36       if self.start.nil?
37         valid = false
38         LOG.log(self, :error, "No start command was specified")
39       end
40       
41       # self-daemonizing processes must specify a stop command
42       if !@tracking_pid && self.stop.nil?
43         valid = false
44         LOG.log(self, :error, "No stop command was specified")
45       end
46       
47       # self-daemonizing processes cannot specify log
48       if !@tracking_pid && self.log
49         valid = false
50         LOG.log(self, :error, "Self-daemonizing processes cannot specify a log file")
51       end
52       
53       # uid must exist if specified
54       if self.uid
55         begin
56           Etc.getpwnam(self.uid)
57         rescue ArgumentError
58           valid = false
59           LOG.log(self, :error, "UID for '#{self.uid}' does not exist")
60         end
61       end
62       
63       # gid must exist if specified
64       if self.gid
65         begin
66           Etc.getgrnam(self.gid)
67         rescue ArgumentError
68           valid = false
69           LOG.log(self, :error, "GID for '#{self.gid}' does not exist")
70         end
71       end
72       
73       valid
74     end
75     
76     # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
77     # No really, trust me. Use the instance variable.
78     def pid_file=(value)
79       @tracking_pid = false
80       @pid_file = value
81     end
82     
83     def pid_file
84       if @pid_file.nil?
85         @tracking_pid = true
86         @pid_file = default_pid_file
87       end
88       @pid_file
89     end
90     
91     def start!
92       call_action(:start)
93     end
94     
95     def stop!
96       call_action(:stop)
97     end
98     
99     def restart!
100       call_action(:restart)
101     end
102     
103     def call_action(action)
104       command = send(action)
105       
106       if action == :stop && command.nil?
107         pid = File.read(self.pid_file).strip.to_i
108         name = self.name
109         command = lambda do
110           LOG.log(self, :info, "#{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             
130       if command.kind_of?(String)
131         # string command
132         # fork/exec to setuid/gid
133         r, w = IO.pipe
134         opid = fork do
135           STDOUT.reopen(w)
136           r.close
137           pid = fork do
138             ::Process.setsid
139             ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid
140             ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid
141             Dir.chdir "/"
142             $0 = command
143             STDIN.reopen "/dev/null"
144             if self.log
145               STDOUT.reopen self.log, "a"
146             else
147               STDOUT.reopen "/dev/null", "a"
148             end
149             STDERR.reopen STDOUT
150             
151             exec command unless command.empty?
152           end
153           puts pid.to_s
154         end
155         
156         ::Process.waitpid(opid, 0)
157         w.close
158         pid = r.gets.chomp
159         
160         if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
161           File.open(default_pid_file, 'w') do |f|
162             f.write pid
163           end
164           
165           @tracking_pid = true
166           @pid_file = default_pid_file
167         end
168         
169       elsif command.kind_of?(Proc)
170         # lambda command
171         command.call
172       else
173         raise NotImplementedError
174       end
175     end
176     
177     def default_pid_file
178       File.join(God.pid_file_directory, "#{self.name}.pid")
179     end
180   end