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