+#! /usr/bin/env python
+
"""Run a subprocess and communicate with it via stdin, stdout, and stderr.
-Requires that platform supports, eg, posix-style os.pipe and os.fork.
+Requires that platform supports, eg, posix-style 'os.pipe' and 'os.fork'
+routines.
Subprocess class features:
- provides detection of subprocess startup failure
- Subprocess objects have nice, informative string rep (as every good object
- ought).
-
- - RecordFile class provides record-oriented IO for file-like stream objects.
-"""
+ ought)."""
-__version__ = "Revision: 1.7 "
+__version__ = "Revision: 1.15 "
-# Id: subproc.py,v 1.7 1998
+# Id: subproc.py,v 1.15 1998/12/14 20:53:16 klm Exp
# Originally by ken manheimer, ken.manheimer@nist.gov, jan 1995.
# Prior art: Initially based python code examples demonstrating usage of pipes
#
# ken.manheimer@nist.gov
+# This is a modified version by Oleg Broytman <phd@phdru.name>.
+# The original version is still preserved at
+# https://www.python.org/ftp/python/contrib-09-Dec-1999/System/subproc.tar.gz
import sys, os, string, time, types
import select
import signal
-SubprocessError = 'SubprocessError'
+class SubprocessError(Exception):
+ pass
+
# You may need to increase execvp_grace_seconds, if you have a large or slow
# path to search:
execvp_grace_seconds = 0.5
"""Fork a subprocess with designated COMMAND (default, self.cmd)."""
if cmd: self.cmd = cmd
else: cmd = self.cmd
+ cmd = string.split(self.cmd)
pRc, cWp = os.pipe() # parent-read-child, child-write-parent
cRp, pWc = os.pipe() # child-read-parent, parent-write-child
pRe, cWe = os.pipe() # parent-read-error, child-write-error
try: os.close(i)
except os.error: pass
- self.run_cmd(cmd)
+ try:
+ os.execvp(cmd[0], cmd)
+ os._exit(1) # Shouldn't get here
+
+ except os.error as e:
+ if self.control_stderr:
+ os.dup2(parentErr, 2) # Reconnect to parent's stdout
+ sys.stderr.write("**execvp failed, '%s'**\n" %
+ str(e))
+ os._exit(1)
os._exit(1) # Shouldn't get here.
else: ### PARENT ###
time.sleep(execvp_grace_seconds)
try:
pid, err = os.waitpid(self.pid, os.WNOHANG)
- except os.error, (errno, msg):
+ except os.error as error:
+ errno, msg = error
if errno == 10:
- raise SubprocessError, \
- "Subprocess '%s' failed." % self.cmd
- raise SubprocessError, \
- "Subprocess failed[%d]: %s" % (errno, msg)
+ self.pid = None
+ raise SubprocessError("Subprocess '%s' failed." % self.cmd)
+ self.pid = None
+ raise SubprocessError("Subprocess failed[%d]: %s" % (errno, msg))
if pid == self.pid:
# child exited already
self.pid == None
sig = err & 0xff
rc = (err & 0xff00) >> 8
if sig:
- raise SubprocessError, (
+ raise SubprocessError(
"child killed by signal %d with a return code of %d"
% (sig, rc))
if rc:
- raise SubprocessError, \
- "child exited with return code %d" % rc
+ self.pid = None
+ raise SubprocessError(
+ "child exited with return code %d" % rc)
# Child may have exited, but not in error, so we won't say
# anything more at this point.
- def run_cmd(self, cmd):
- cmd = string.split(self.cmd)
-
- try:
- os.execvp(cmd[0], cmd)
- os._exit(1) # Shouldn't get here
-
- except os.error, e:
- if self.control_stderr:
- os.dup2(parentErr, 2) # Reconnect to parent's stdout
- sys.stderr.write("**execvp failed, '%s'**\n" %
- str(e))
- os._exit(1)
-
-
### Write input to subprocess ###
def write(self, str):
"""Write a STRING to the subprocess."""
if not self.pid:
- raise SubprocessError, "no child" # ===>
+ raise SubprocessError("no child") # ===>
if select.select([],self.toChild_fdlist,[],0)[1]:
self.toChild.write(str)
self.toChild.flush()
else:
# XXX Can write-buffer full be handled better??
- raise IOError, "write to %s blocked" % self # ===>
+ raise IOError("write to %s blocked" % self) # ===>
def writeline(self, line=''):
"""Write STRING, with added newline termination, to subprocess."""
got0 = self.readPendingChars(n)
got = got + got0
n = n - len(got0)
- return got
+ return got
def readPendingChars(self, max=None):
"""Read all currently pending subprocess output as a single string."""
return self.readbuf.readPendingChars(max)
if self.control_stderr:
return self.errbuf.readPendingChars()
else:
- raise SubprocessError, "Haven't grabbed subprocess error stream."
+ raise SubprocessError("Haven't grabbed subprocess error stream.")
def readPendingLine(self):
"""Read currently pending subprocess output, up to a complete line
if self.control_stderr:
return self.errbuf.readPendingLine()
else:
- raise SubprocessError, "Haven't grabbed subprocess error stream."
+ raise SubprocessError("Haven't grabbed subprocess error stream.")
def readline(self):
"""Return next complete line of subprocess output, blocking until
if self.control_stderr:
return self.errbuf.readline()
else:
- raise SubprocessError, "Haven't grabbed subprocess error stream."
+ raise SubprocessError("Haven't grabbed subprocess error stream.")
### Subprocess Control ###
os.kill(self.pid, signal.SIGSTOP)
except os.error:
if verbose:
- print "Stop failed for '%s' - '%s'" % (self.cmd, sys.exc_value)
+ print("Stop failed for '%s' - '%s'" % (self.cmd, sys.exc_value))
return 0
- if verbose: print "Stopped '%s'" % self.cmd
+ if verbose: print("Stopped '%s'" % self.cmd)
return 'stopped'
def cont(self, verbose=0):
os.kill(self.pid, signal.SIGCONT)
except os.error:
if verbose:
- print ("Continue failed for '%s' - '%s'" %
- (self.cmd, sys.exc_value))
+ print(("Continue failed for '%s' - '%s'" %
+ (self.cmd, sys.exc_value)))
return 0
- if verbose: print "Continued '%s'" % self.cmd
+ if verbose: print("Continued '%s'" % self.cmd)
return 'continued'
def die(self):
SubprocessError is raised if process is not successfully killed."""
if not self.pid:
- raise SubprocessError, "No process" # ===>
+ raise SubprocessError("No process") # ===>
elif not self.cont():
- raise SubprocessError, "Can't signal subproc %s" % self # ===>
+ raise SubprocessError("Can't signal subproc %s" % self) # ===>
# Try sending first a TERM and then a KILL signal.
keep_trying = 1
# WNOHANG == 1 on sunos, presumably same elsewhere.
if os.waitpid(self.pid, os.WNOHANG):
if self.expire_noisily:
- print ("\n(%s subproc %d '%s' / %s)" %
+ print(("\n(%s subproc %d '%s' / %s)" %
(sig[0], self.pid, self.cmd,
- hex(id(self))[2:]))
+ hex(id(self))[2:])))
for i in self.pipefiles:
- os.close(i)
+ try:
+ fp = os.fdopen(i).close()
+ except OSError:
+ pass
+ del self.pipefiles[:]
self.pid = 0
return None # ===>
time.sleep(.1)
# Only got here if subprocess is not gone:
- raise (SubprocessError,
- ("Failed kill of subproc %d, '%s', with signals %s" %
- (self.pid, self.cmd, map(lambda(x): x[0], sigs))))
+ raise SubprocessError(
+ "Failed kill of subproc %d, '%s', with signals %s" %
+ (self.pid, self.cmd, map(lambda x: x[0], sigs)))
def __del__(self):
"""Terminate the subprocess"""
status = self.status()
return '<Subprocess ' + status + ', at ' + hex(id(self))[2:] + '>'
-# The name of the class is a pun; it is short for "Process", but it also appeals
-# to the word "Procedure"
-class Subproc(Subprocess):
- def run_cmd(self, cmd):
- apply(cmd[0], cmd[1:])
- os._exit(1)
-
#############################################################################
##### Non-blocking read operations #####
#############################################################################
got = ""
if self.buf:
- got, self.buf = self.buf, ''
- return got # ===>
+ if (max > 0) and (len(self.buf) > max):
+ got = self.buf[0:max]
+ self.buf = self.buf[max:]
+ else:
+ got, self.buf = self.buf, ''
+ return got
if self.eof:
return ''
try:
l = string.atoi(line)
except ValueError:
- raise IOError, ("corrupt %s file structure"
- % self.__class__.__name__)
+ raise IOError(("corrupt %s file structure"
+ % self.__class__.__name__))
return f.read(l)
else:
# EOF.
if hasattr(f, attr):
return getattr(f, attr)
else:
- raise AttributeError, attr
+ raise AttributeError(attr)
def __repr__(self):
return "<%s of %s at %s>" % (self.__class__.__name__,
c.write(s)
c.seek(0)
r = c.read()
- show = " start:\t %s\n end:\t %s\n" % (`s`, `r`)
+ show = " start:\t %s\n end:\t %s\n" % (repr(s), repr(r))
if r != s:
- raise IOError, "String distorted:\n%s" % show
+ raise IOError("String distorted:\n%s" % show)
#############################################################################
##### An example subprocess interfaces #####
try:
self.proc = Subprocess('ph', 1)
except:
- raise SubprocessError, ('failure starting ph: %s' % # ===>
+ raise SubprocessError('failure starting ph: %s' % # ===>
str(sys.exc_value))
def query(self, q):
if not response:
return got # ===>
elif type(response) == types.StringType:
- raise ValueError, "ph failed match: '%s'" % response # ===>
+ raise ValueError("ph failed match: '%s'" % response) # ===>
for line in response:
# convert to a dict:
line = string.splitfields(line, ':')
it[string.strip(line[0])] = (
string.strip(string.join(line[1:])))
-
+
def getreply(self):
"""Consume next response from ph, returning list of lines or string
err."""
nextChar = self.proc.waitForPendingChar(60)
if not nextChar:
- raise SubprocessError, 'ph subprocess not responding' # ===>
+ raise SubprocessError('ph subprocess not responding') # ===>
elif nextChar == '-':
# dashed line - discard it, and continue reading:
self.proc.readline()
got = string.splitfields(got, '\n')[-1]
if got == 'ph> ': return # Ok. ===>
time.sleep(pause)
- raise SubprocessError, ('ph not responding within %s secs' %
+ raise SubprocessError('ph not responding within %s secs' %
pause * maxIter)
#############################################################################
#############################################################################
def test(p=0):
- print "\tOpening subprocess:"
- p = Subprocess('cat', 1) # set to expire noisily...
- print p
- print "\tOpening bogus subprocess, should fail:"
+ print("\tOpening bogus subprocess, should fail:")
try:
b = Subprocess('/', 1)
- print "\tOops! Null-named subprocess startup *succeeded*?!?"
+ print("\tOops! Null-named subprocess startup *succeeded*?!?")
except SubprocessError:
- print "\t...yep, it failed."
- print '\tWrite, then read, two newline-teriminated lines, using readline:'
+ print("\t...yep, it failed.")
+ print("\tOpening cat subprocess:")
+ p = Subprocess('cat', 1) # set to expire noisily...
+ print(p)
+ print('\tWrite, then read, two newline-teriminated lines, using readline:')
p.write('first full line written\n'); p.write('second.\n')
- print `p.readline()`
- print `p.readline()`
- print '\tThree lines, last sans newline, read using combination:'
+ print(repr(p.readline()))
+ print(repr(p.readline()))
+ print('\tThree lines, last sans newline, read using combination:')
p.write('first\n'); p.write('second\n'); p.write('third, (no cr)')
- print '\tFirst line via readline:'
- print `p.readline()`
- print '\tRest via readPendingChars:'
- print p.readPendingChars()
- print "\tStopping then continuing subprocess (verbose):"
+ print('\tFirst line via readline:')
+ print(repr(p.readline()))
+ print('\tRest via readPendingChars:')
+ print(p.readPendingChars())
+ print("\tStopping then continuing subprocess (verbose):")
if not p.stop(1): # verbose stop
- print '\t** Stop seems to have failed!'
+ print('\t** Stop seems to have failed!')
else:
- print '\tWriting line while subprocess is paused...'
+ print('\tWriting line while subprocess is paused...')
p.write('written while subprocess paused\n')
- print '\tNonblocking read of paused subprocess (should be empty):'
- print p.readPendingChars()
- print '\tContinuing subprocess (verbose):'
+ print('\tNonblocking read of paused subprocess (should be empty):')
+ print(p.readPendingChars())
+ print('\tContinuing subprocess (verbose):')
if not p.cont(1): # verbose continue
- print '\t** Continue seems to have failed! Probly lost subproc...'
+ print('\t** Continue seems to have failed! Probly lost subproc...')
return p
else:
- print '\tReading accumulated line, blocking read:'
- print p.readline()
- print "\tDeleting subproc, which was set to die noisily:"
+ print('\tReading accumulated line, blocking read:')
+ print(p.readline())
+ print("\tDeleting subproc, which was set to die noisily:")
del p
- print "\tDone."
+ print("\tDone.")
return None
+
+if __name__ == '__main__':
+ test()