From: <pj...@us...> - 2009-04-03 06:02:46
|
Revision: 6153 http://jython.svn.sourceforge.net/jython/?rev=6153&view=rev Author: pjenvey Date: 2009-04-03 06:02:36 +0000 (Fri, 03 Apr 2009) Log Message: ----------- from: http://svn.python.org/projects/python/branches/release25-maint/Lib/popen2.py@50759 Modified Paths: -------------- trunk/jython/Lib/popen2.py Modified: trunk/jython/Lib/popen2.py =================================================================== --- trunk/jython/Lib/popen2.py 2009-04-03 05:54:58 UTC (rev 6152) +++ trunk/jython/Lib/popen2.py 2009-04-03 06:02:36 UTC (rev 6153) @@ -1,269 +1,213 @@ -""" -popen2.py +"""Spawn a command with pipes to its stdin, stdout, and optionally stderr. -Implement popen2 module functionality for Jython. - -Note that the popen* methods in this module follow the return value -ordering of the Python popen2.popen* methods: - - fromChild, toChild = popen2.popen2(...) - fromChild, toChild, errorFromChild = popen2.popen3(...) - fromChildInclError, toChild = popen2.popen4(...) - -The os.popen* methods are more natural as follows: - - toChild, fromChild = os.popen2(...) - toChild, fromChild, errorFromChild = os.popen3(...) - toChild, fromChildInclError = os.popen4(...) - -The Popen3 and Popen4 classes allow users to poll() or wait() for -child processes to terminate. +The normal os.popen(cmd, mode) call spawns a shell command and provides a +file interface to just the input or output of the process depending on +whether mode is 'r' or 'w'. This module provides the functions popen2(cmd) +and popen3(cmd) which return two or three pipes to the spawned command. """ -import jarray -from java.lang import System -from java.util import Vector -from java.io import BufferedOutputStream -from java.io import BufferedInputStream -from java.io import PipedOutputStream -from java.io import PipedInputStream -from javashell import shellexecute -from org.python.core.util import FileUtil +import os +import sys +__all__ = ["popen2", "popen3", "popen4"] -__all__ = ["popen", "popen2", "popen3", "popen4", "Popen3", "Popen4"] +try: + MAXFD = os.sysconf('SC_OPEN_MAX') +except (AttributeError, ValueError): + MAXFD = 256 _active = [] def _cleanup(): - """For CPython compatibility""" - pass + for inst in _active[:]: + if inst.poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass -class _ProcessFile: - """Python file object that returns the process exit status from - the close method. - """ - def __init__(self, stream, process, name): - self._file = FileUtil.wrap(stream, 0) - self._process = process - - def __getattr__(self, name): - return getattr(self._file, name) - - def __repr__(self): - return `self._file` - - def close(self): - self._file.close() - return self._process.waitFor() or None - class Popen3: """Class representing a child process. Normally instances are created by the factory functions popen2() and popen3().""" - sts = -1 # Child not completed yet - childWaiter = None - count = 0 - - def __init__(self, cmd, capturestderr=0, bufsize=-1): + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): """The parameter 'cmd' is the shell command to execute in a - sub-process. Can be either a sequence of executable - and arguments, or a shell command. - The 'capturestderr' flag, if true, specifies that - the object should capture standard error output of the child process. - The default is false. If the 'bufsize' parameter is specified, it - specifies the size of the I/O buffers to/from the child process. - """ - self.process = shellexecute( cmd ) - self._tochild = self.process.getOutputStream() - self._fromchild = self.process.getInputStream() + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() if capturestderr: - self._childerr = self.process.getErrorStream() + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) else: - self._childerr = None - import threading - self.childWaiterLock = threading.Lock() + self.childerr = None - if bufsize > 0: - self._tochild = BufferedOutputStream( self._tochild, bufsize ) - self._fromchild = BufferedInputStream( self._fromchild, bufsize ) - if self._childerr: - self._childerr = BufferedInputStream( - self._childerr, - bufsize - ) - - self.tochild = FileUtil.wrap(self._tochild, 0) - self.fromchild = FileUtil.wrap(self._fromchild, 0) - if self._childerr: - self.childerr = FileUtil.wrap(self._childerr, 0) + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) - def _startChildWaiter(self): - """Start a subthread that waits for the child process to exit.""" - self.childWaiterLock.acquire() + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + for i in xrange(3, MAXFD): + try: + os.close(i) + except OSError: + pass try: - if not self.childWaiter: - import threading - self.childWaiter = threading.Thread( - target=self.wait, - name="ChildWaiter %s" % self.process, - args=() - ) - self.childWaiter.setDaemon( 1 ) - self.childWaiter.start() + os.execvp(cmd[0], cmd) finally: - self.childWaiterLock.release() + os._exit(1) - def poll(self): + def poll(self, _deadstate=None): """Return the exit status of the child process if it has finished, or -1 if it hasn't finished yet.""" - if self.sts < 0 and not self.childWaiter: - self._startChildWaiter() - self.childWaiter.join( .1 ) + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate return self.sts def wait(self): """Wait for and return the exit status of the child process.""" - self.sts = self.process.waitFor() - # some processes won't terminate until tochild stream is - # closed, but that's really the responsibility of the caller + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts return self.sts -def _makeReaderThread( stream, outfunc, bufsize, name=None, postFunc=None ): - """Create a thread that reads the stream, calling outfunc for each block, - and calling postFunc when the end of stream is reached. - """ - Popen3.count += 1 - name = name or str( Popen3.count ) - threadName = "StreamReader %s" % name - import threading - reader = threading.Thread( - target=_readStream, - name=threadName, - args=( stream, outfunc, bufsize, postFunc ) - ) - reader.setDaemon( 1 ) - reader.start() - return reader - -def _readStream( instream, outfunc, bufsize, postFunc=None ): - """Read instream, calling outfunc( buf, 0, count ) with each block. - Copy streams by passing destStream.write as the outfunc. - postFunc is called when the end of instream is reached. - """ - bufsize = bufsize < 1 and 4096 or bufsize - buf = jarray.zeros( bufsize, 'b' ) - total = 0 - while 1: - count = instream.read( buf ) - if -1 == count: - instream.close() - if postFunc: postFunc() - break - else: - total += count - outfunc( buf, 0, count ) - return total - class Popen4(Popen3): - """Popen object that joins the stdout and stderr streams into a single - output stream.""" childerr = None def __init__(self, cmd, bufsize=-1): - Popen3.__init__( self, cmd, 1, bufsize ) - self.closed = Vector() # use a vector for synchronization close() - self.fromchild = self._join( - self._fromchild, - self._childerr, - bufsize - ) + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + os.dup2(c2pwrite, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) - def _join( self, stdout, stderr, bufsize ): - """create a stream that joins two output streams""" - self._pipeOut = PipedOutputStream() - joinedStream = PipedInputStream( self._pipeOut ) - self._outReader = _makeReaderThread( - stdout, - self._pipeOut.write, - bufsize, - "%s-stdout" % self.process, - self._close - ) - self._errReader = _makeReaderThread( - stderr, - self._pipeOut.write, - bufsize, - "%s-stderr" % self.process, - self._close - ) - return FileUtil.wrap(joinedStream, 0) - def _close( self ): - """Must be closed twice (once for each of the two joined pipes)""" - self.closed.add( None ) - if self.closed.size() > 1: - self._pipeOut.close() +if sys.platform[:3] == "win" or sys.platform == "os2emx": + # Some things don't make sense on non-Unix platforms. + del Popen3, Popen4 -def popen(path, mode='r', bufsize=-1): - p = Popen3( path, 0, bufsize ) - if mode == 'r': - return _ProcessFile(p.fromchild, p.process, path) - elif mode == 'w': - return _ProcessFile(p.tochild, p.process, path) - else: - raise OSError(0, "Invalid mode", mode) + def popen2(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin) are returned.""" + w, r = os.popen2(cmd, mode, bufsize) + return r, w -def popen2(path, mode="t", bufsize=-1): - p = Popen3(path, 0, bufsize) - return p.fromchild, p.tochild + def popen3(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin, child_stderr) are returned.""" + w, r, e = os.popen3(cmd, mode, bufsize) + return r, w, e -def popen3(path, mode="t", bufsize=-1): - p = Popen3(path, 1, bufsize) - return p.fromchild, p.tochild, p.childerr + def popen4(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout_stderr, child_stdin) are returned.""" + w, r = os.popen4(cmd, mode, bufsize) + return r, w +else: + def popen2(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin) are returned.""" + inst = Popen3(cmd, False, bufsize) + return inst.fromchild, inst.tochild -def popen4(path, mode="t", bufsize=-1): - p = Popen4(path, bufsize) - return p.fromchild, p.tochild + def popen3(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin, child_stderr) are returned.""" + inst = Popen3(cmd, True, bufsize) + return inst.fromchild, inst.tochild, inst.childerr -def system( cmd ): - """Imitate the standard library 'system' call. - Execute 'cmd' in a shell, and send output to stdout & stderr. + def popen4(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout_stderr, child_stdin) are returned.""" + inst = Popen4(cmd, bufsize) + return inst.fromchild, inst.tochild - This is in popen2 only because its Jython implementation is similar to - that of the popen functions. - """ - bufsize = 4096 - # this uses some Popen3 internals, and thus belongs in popen3 - # os.system should also be this function - p = Popen3( cmd, 1, bufsize) - p.tochild.close() - - # read stderr in separate thread - errReader = _makeReaderThread( - p._childerr, - System.err.write, - bufsize, - "stderr" - ) + __all__.extend(["Popen3", "Popen4"]) - # read stdin in main thread - _readStream( - p._fromchild, - System.out.write, - bufsize - ) - - status = p.wait() - return status - def _test(): - # _test comes from python22/lib/popen2.py + # When the test runs, there shouldn't be any open pipes + _cleanup() + assert not _active, "Active pipes when test starts " + repr([c.cmd for c in _active]) cmd = "cat" teststr = "ab cd\n" - import os - if os.name in [ "nt", "java" ]: + if os.name == "nt": cmd = "more" # "more" doesn't act the same way across Windows flavors, # sometimes adding an extra newline at the start or the @@ -275,7 +219,7 @@ w.close() got = r.read() if got.strip() != expected: - raise ValueError("wrote %s read %s" % (teststr, got)) + raise ValueError("wrote %r read %r" % (teststr, got)) print "testing popen3..." try: r, w, e = popen3([cmd]) @@ -284,26 +228,17 @@ w.write(teststr) w.close() got = r.read() - err = e.read() if got.strip() != expected: - raise ValueError("wrote %s read %s, error %s" % (teststr, got, err )) - if err: - raise ValueError("unexected %s on stderr" % err ) -# this portion of the test is inapplicable to the Jython implementation -# for inst in _active[:]: -# inst.wait() -# if _active: -# raise ValueError("_active not empty") + raise ValueError("wrote %r read %r" % (teststr, got)) + got = e.read() + if got: + raise ValueError("unexpected %r on stderr" % (got,)) + for inst in _active[:]: + inst.wait() + _cleanup() + if _active: + raise ValueError("_active not empty") print "All OK" - p = Popen3( cmd ) - q = "This is\na test of\nwriting\n" - p.tochild.write( q ) - p.tochild.close() - r = p.fromchild.read() - x = p.poll() - assert x == 0 - assert r.strip() == q.strip() - if __name__ == '__main__': _test() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |