3 # Util classes for Nagios plugins
9 #==========================================================================
13 # (C) Rob W.W. Hooft, Nonius BV, 1998
15 # Contact r.hooft@euromail.net for questions/suggestions.
16 # See: <http://starship.python.net/crew/hooft/>
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
25 import sys
,os
,signal
,time
,string
27 class error(Exception):
30 class _ready(Exception):
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:
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.
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).
69 __init__, __str__, Run, Wait, Kill, Done, Status.
71 def __init__(self
,command
):
74 command: the command to run, in the form of a string,
75 or a tuple or list of words.
77 if type(command
)==type(''):
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
)
85 raise error("command must be tuple, list, or string")
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.
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
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
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.
115 raise error("Second run on task forbidden")
118 for fn
in range(3,256): # Close all non-standard files in a safe way
124 # jaclu@galdrion.com 2000-07-14
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.
133 if stdout
: # Replace stdout by file
135 i
=os
.open(stdout
,os
.O_CREAT|os
.O_WRONLY|os
.O_TRUNC
,0666)
137 sys
.stderr
.write("stdout not opened on 1!\n")
138 if stdin
: # Replace stdin by file
140 i
=os
.open(stdin
,os
.O_RDONLY
)
142 sys
.stderr
.write("stdin not opened on 0!\n")
143 if stderr
: # Replace stderr by file
145 i
=os
.open(stderr
,os
.O_CREAT|os
.O_WRONLY|os
.O_TRUNC
,0666)
147 sys
.stdout
.write("stderr not opened on 2!\n")
150 os
.execv('/bin/sh',('sh','-c',self
.cmd
+'&'))
152 os
.execv('/bin/sh',('sh','-c',self
.cmd
))
154 os
.execvp(self
.words
[0],self
.words
)
157 #sys.stderr.write("Subprocess '%s' execution failed!\n"%self.cmd)
162 # Should complete "immediately"
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.
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
179 interval=0.1: The interval (in seconds) with which the 'idlefunc'
180 (if any) will be called.
182 the exit status of the subprocess (0 if successful).
184 if self
.status
!=None:
187 if callable(idlefunc
):
190 pid
,status
=os
.waitpid(self
.pid
,os
.WNOHANG
)
197 except KeyboardInterrupt:
198 # Send the interrupt to the inferior process.
199 self
.Kill(signal
=signal
.SIGINT
)
201 raise error("Non-callable idle function")
205 pid
,status
=os
.waitpid(self
.pid
,0)
208 except KeyboardInterrupt:
209 # Send the interrupt to the inferior process.
210 self
.Kill(signal
=signal
.SIGINT
)
212 def Kill(self
,signal
=signal
.SIGTERM
):
213 """Send a signal to the running subprocess.
215 signal=SIGTERM: number of the signal to send.
220 if self
.status
==None:
221 # Only if it is not already finished
222 return os
.kill(self
.pid
,signal
)
225 """Ask whether the process has already finished.
227 1: yes, the process has finished.
228 0: no, the process has not finished yet.
230 if self
.status
!=None:
233 pid
,status
=os
.waitpid(self
.pid
,os
.WNOHANG
)
235 #print "OK:",pid,status
239 #print "NOK:",pid,status
243 """Ask for the status of the task.
245 None: process has not finished yet (maybe not even started).
246 any integer: process exit status.
253 if self
.status
!=None:
254 s2
="done, exit status=%d"%self
.status
259 return "<%s: '%s', %s>"%(self
.__class
__.__name
__,self
.cmd
,s2
)
262 #==========================================================================
265 # Class: TimeoutHandler
267 # Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
269 # Version: 1.0 2000-07-14
272 # On init, suply a call-back kill_func that should be called on timeout
274 # Make sure that what ever you are doing is calling Check periodically
276 # To check if timeout was triggered call WasTimeOut returns (true/false)
281 class 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
)
291 'Call this periodically to check for time-out.'
293 sys
.stdout
.write('.')
295 if time
.time()>=self
.stop_time
:
299 'Trigger the time-out callback.'
302 print 'Timeout, aborting'
305 def WasTimeOut(self
):
306 'Indicates if timeout was triggered 1=yes, 0=no.'
309 print 'call duration: %.2f seconds' % (time
.time()-self
.start_time
)