summaryrefslogtreecommitdiffstats
path: root/contrib/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/utils.py')
-rw-r--r--contrib/utils.py310
1 files changed, 310 insertions, 0 deletions
diff --git a/contrib/utils.py b/contrib/utils.py
new file mode 100644
index 0000000..73d795c
--- /dev/null
+++ b/contrib/utils.py
@@ -0,0 +1,310 @@
1#
2#
3# Util classes for Nagios plugins
4#
5#
6
7
8
9#==========================================================================
10#
11# Version: = '$Id$'
12#
13# (C) Rob W.W. Hooft, Nonius BV, 1998
14#
15# Contact r.hooft@euromail.net for questions/suggestions.
16# See: <http://starship.python.net/crew/hooft/>
17# Distribute freely.
18#
19# jaclu@galdrion.com 2000-07-14
20# Some changes in error handling of Run() to avoid error garbage
21# when used from Nagios plugins
22# I also removed the following functions: AbortableWait() and _buttonkill()
23# since they are only usable with Tkinter
24
25import sys,os,signal,time,string
26
27class error(Exception):
28 pass
29
30class _ready(Exception):
31 pass
32
33def which(filename):
34 """Find the file 'filename' in the execution path. If no executable
35 file is found, return None"""
36 for dir in string.split(os.environ['PATH'],os.pathsep):
37 fn=os.path.join(dir,filename)
38 if os.path.exists(fn):
39 if os.stat(fn)[0]&0111:
40 return fn
41 else:
42 return None
43
44class Task:
45 """Manage asynchronous subprocess tasks.
46 This differs from the 'subproc' package!
47 - 'subproc' connects to the subprocess via pipes
48 - 'task' lets the subprocess run autonomously.
49 After starting the task, we can just:
50 - ask whether it is finished yet
51 - wait until it is finished
52 - perform an 'idle' task (e.g. Tkinter's mainloop) while waiting for
53 subprocess termination
54 - kill the subprocess with a specific signal
55 - ask for the exit code.
56 Summarizing:
57 - 'subproc' is a sophisticated os.popen()
58 - 'task' is a sophisticated os.system()
59 Another difference of task with 'subproc':
60 - If the Task() object is deleted, before the subprocess status
61 was retrieved, the child process will stay.
62 It will never be waited for (i.e., the process will turn into
63 a zombie. Not a good idea in general).
64
65 Public data:
66 None.
67
68 Public methods:
69 __init__, __str__, Run, Wait, Kill, Done, Status.
70 """
71 def __init__(self,command):
72 """Constructor.
73 arguments:
74 command: the command to run, in the form of a string,
75 or a tuple or list of words.
76 """
77 if type(command)==type(''):
78 self.cmd=command
79 self.words=string.split(command)
80 elif type(command)==type([]) or type(command)==type(()):
81 # Surround each word by ' '. Limitation: words cannot contain ' chars
82 self.cmd="'"+string.join(command,"' '")+"'"
83 self.words=tuple(command)
84 else:
85 raise error("command must be tuple, list, or string")
86 self.pid=None
87 self.status=None
88
89 def Run(self,usesh=0,detach=0,stdout=None,stdin=None,stderr=None):
90 """Actually run the process.
91 This method should be called exactly once.
92 optional arguments:
93 usesh=0: if 1, run 'sh -c command', if 0, split the
94 command into words, and run it by ourselves.
95 If usesh=1, the 'Kill' method might not do what
96 you want (it will kill the 'sh' process, not the
97 command).
98 detach=0: if 1, run 'sh -c 'command&' (regardless of
99 'usesh'). Since the 'sh' process will immediately
100 terminate, the task created will be inherited by
101 'init', so you can safely forget it. Remember that if
102 detach=1, Kill(), Done() and Status() will manipulate
103 the 'sh' process; there is no way to find out about the
104 detached process.
105 stdout=None: filename to use as stdout for the child process.
106 If None, the stdout of the parent will be used.
107 stdin= None: filename to use as stdin for the child process.
108 If None, the stdin of the parent will be used.
109 stderr=None: filename to use as stderr for the child process.
110 If None, the stderr of the parent will be used.
111 return value:
112 None
113 """
114 if self.pid!=None:
115 raise error("Second run on task forbidden")
116 self.pid=os.fork()
117 if not self.pid:
118 for fn in range(3,256): # Close all non-standard files in a safe way
119 try:
120 os.close(fn)
121 except os.error:
122 pass
123 #
124 # jaclu@galdrion.com 2000-07-14
125 #
126 # I changed this bit somewhat, since Nagios plugins
127 # should send only limited errors to the caller
128 # The original setup here corupted output when there was an error.
129 # Instead the caller should check result of Wait() and anything
130 # not zero should be reported as a failure.
131 #
132 try:
133 if stdout: # Replace stdout by file
134 os.close(1)
135 i=os.open(stdout,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666)
136 if i!=1:
137 sys.stderr.write("stdout not opened on 1!\n")
138 if stdin: # Replace stdin by file
139 os.close(0)
140 i=os.open(stdin,os.O_RDONLY)
141 if i!=0:
142 sys.stderr.write("stdin not opened on 0!\n")
143 if stderr: # Replace stderr by file
144 os.close(2)
145 i=os.open(stderr,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666)
146 if i!=2:
147 sys.stdout.write("stderr not opened on 2!\n")
148 #try:
149 if detach:
150 os.execv('/bin/sh',('sh','-c',self.cmd+'&'))
151 elif usesh:
152 os.execv('/bin/sh',('sh','-c',self.cmd))
153 else:
154 os.execvp(self.words[0],self.words)
155 except:
156 #print self.words
157 #sys.stderr.write("Subprocess '%s' execution failed!\n"%self.cmd)
158 sys.exit(1)
159 else:
160 # Mother process
161 if detach:
162 # Should complete "immediately"
163 self.Wait()
164
165 def Wait(self,idlefunc=None,interval=0.1):
166 """Wait for the subprocess to terminate.
167 If the process has already terminated, this function will return
168 immediately without raising an error.
169 optional arguments:
170 idlefunc=None: a callable object (function, class, bound method)
171 that will be called every 0.1 second (or see
172 the 'interval' variable) while waiting for
173 the subprocess to terminate. This can be the
174 Tkinter 'update' procedure, such that the GUI
175 doesn't die during the run. If this is set to
176 'None', the process will really wait. idlefunc
177 should ideally not take a very long time to
178 complete...
179 interval=0.1: The interval (in seconds) with which the 'idlefunc'
180 (if any) will be called.
181 return value:
182 the exit status of the subprocess (0 if successful).
183 """
184 if self.status!=None:
185 # Already finished
186 return self.status
187 if callable(idlefunc):
188 while 1:
189 try:
190 pid,status=os.waitpid(self.pid,os.WNOHANG)
191 if pid==self.pid:
192 self.status=status
193 return status
194 else:
195 idlefunc()
196 time.sleep(interval)
197 except KeyboardInterrupt:
198 # Send the interrupt to the inferior process.
199 self.Kill(signal=signal.SIGINT)
200 elif idlefunc:
201 raise error("Non-callable idle function")
202 else:
203 while 1:
204 try:
205 pid,status=os.waitpid(self.pid,0)
206 self.status=status
207 return status
208 except KeyboardInterrupt:
209 # Send the interrupt to the inferior process.
210 self.Kill(signal=signal.SIGINT)
211
212 def Kill(self,signal=signal.SIGTERM):
213 """Send a signal to the running subprocess.
214 optional arguments:
215 signal=SIGTERM: number of the signal to send.
216 (see os.kill)
217 return value:
218 see os.kill()
219 """
220 if self.status==None:
221 # Only if it is not already finished
222 return os.kill(self.pid,signal)
223
224 def Done(self):
225 """Ask whether the process has already finished.
226 return value:
227 1: yes, the process has finished.
228 0: no, the process has not finished yet.
229 """
230 if self.status!=None:
231 return 1
232 else:
233 pid,status=os.waitpid(self.pid,os.WNOHANG)
234 if pid==self.pid:
235 #print "OK:",pid,status
236 self.status=status
237 return 1
238 else:
239 #print "NOK:",pid,status
240 return 0
241
242 def Status(self):
243 """Ask for the status of the task.
244 return value:
245 None: process has not finished yet (maybe not even started).
246 any integer: process exit status.
247 """
248 self.Done()
249 return self.status
250
251 def __str__(self):
252 if self.pid!=None:
253 if self.status!=None:
254 s2="done, exit status=%d"%self.status
255 else:
256 s2="running"
257 else:
258 s2="prepared"
259 return "<%s: '%s', %s>"%(self.__class__.__name__,self.cmd,s2)
260
261
262#==========================================================================
263#
264#
265# Class: TimeoutHandler
266# License: GPL
267# Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
268#
269# Version: 1.0 2000-07-14
270#
271# Description:
272# On init, suply a call-back kill_func that should be called on timeout
273#
274# Make sure that what ever you are doing is calling Check periodically
275#
276# To check if timeout was triggered call WasTimeOut returns (true/false)
277#
278
279import time,sys
280
281class TimeoutHandler:
282 def __init__(self,kill_func,time_to_live=10,debug=0):
283 'Generic time-out handler.'
284 self.kill_func=kill_func
285 self.start_time=time.time()
286 self.stop_time=+self.start_time+int(time_to_live)
287 self.debug=debug
288 self.aborted=0
289
290 def Check(self):
291 'Call this periodically to check for time-out.'
292 if self.debug:
293 sys.stdout.write('.')
294 sys.stdout.flush()
295 if time.time()>=self.stop_time:
296 self.TimeOut()
297
298 def TimeOut(self):
299 'Trigger the time-out callback.'
300 self.aborted=1
301 if self.debug:
302 print 'Timeout, aborting'
303 self.kill_func()
304
305 def WasTimeOut(self):
306 'Indicates if timeout was triggered 1=yes, 0=no.'
307 if self.debug:
308 print ''
309 print 'call duration: %.2f seconds' % (time.time()-self.start_time)
310 return self.aborted