[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-120]

add stderr support methods
big embarrassment: i didn't read the ssh2 docs close enough, and all this
time paramiko wasn't handling "extended_data" packets, which contain stderr
output.

so now, several new functions: recv_stderr_ready() and recv_stderr() to
mirror recv_ready() and recv(), and set_combined_stderr() to force stderr
to be combined into stdout.  also, makefile_stderr() to create a fake file
object to represent stderr.
This commit is contained in:
Robey Pointer 2004-12-10 08:25:28 +00:00
parent fb54934726
commit 37892fc0c7
1 changed files with 159 additions and 13 deletions

View File

@ -72,20 +72,23 @@ class Channel (object):
self.eof_received = 0 self.eof_received = 0
self.eof_sent = 0 self.eof_sent = 0
self.in_buffer = '' self.in_buffer = ''
self.in_stderr_buffer = ''
self.timeout = None self.timeout = None
self.closed = False self.closed = False
self.ultra_debug = False self.ultra_debug = False
self.lock = threading.Lock() self.lock = threading.Lock()
self.in_buffer_cv = threading.Condition(self.lock) self.in_buffer_cv = threading.Condition(self.lock)
self.in_stderr_buffer_cv = threading.Condition(self.lock)
self.out_buffer_cv = threading.Condition(self.lock) self.out_buffer_cv = threading.Condition(self.lock)
self.name = str(chanid) self.name = str(chanid)
self.logger = logging.getLogger('paramiko.chan.' + str(chanid)) self.logger = logging.getLogger('paramiko.chan.' + str(chanid))
self.pipe_rfd = self.pipe_wfd = None self.pipe_rfd = self.pipe_wfd = None
self.event = threading.Event() self.event = threading.Event()
self.combine_stderr = False
def __repr__(self): def __repr__(self):
""" """
Returns a string representation of this object, for debugging. Return a string representation of this object, for debugging.
@rtype: str @rtype: str
""" """
@ -108,7 +111,9 @@ class Channel (object):
""" """
Request a pseudo-terminal from the server. This is usually used right Request a pseudo-terminal from the server. This is usually used right
after creating a client channel, to ask the server to provide some after creating a client channel, to ask the server to provide some
basic terminal semantics for the next command you execute. basic terminal semantics for a shell invoked with L{invoke_shell}.
It isn't necessary (or desirable) to call this method if you're going
to exectue a single command with L{exec_command}.
@param term: the terminal type to emulate (for example, C{'vt100'}). @param term: the terminal type to emulate (for example, C{'vt100'}).
@type term: str @type term: str
@ -144,8 +149,12 @@ class Channel (object):
def invoke_shell(self): def invoke_shell(self):
""" """
Request an interactive shell session on this channel. If the server Request an interactive shell session on this channel. If the server
allows it, the channel will then be directly connected to the stdin allows it, the channel will then be directly connected to the stdin,
and stdout of the shell. stdout, and stderr of the shell.
Normally you would call L{get_pty} before this, in which case the
shell will operate through the pty, and the channel will be connected
to the stdin and stdout of the pty.
@return: C{True} if the operation succeeded; C{False} if not. @return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool @rtype: bool
@ -169,8 +178,8 @@ class Channel (object):
def exec_command(self, command): def exec_command(self, command):
""" """
Execute a command on the server. If the server allows it, the channel Execute a command on the server. If the server allows it, the channel
will then be directly connected to the stdin and stdout of the command will then be directly connected to the stdin, stdout, and stderr of
being executed. the command being executed.
@param command: a shell command to execute. @param command: a shell command to execute.
@type command: str @type command: str
@ -296,6 +305,32 @@ class Channel (object):
@since: ivysaur @since: ivysaur
""" """
return self.chanid return self.chanid
def set_combine_stderr(self, combine):
"""
Set whether stderr should be combined into stdout on this channel.
The default is C{False}, but in some cases it may be convenient to
have both streams combined.
If this is C{False}, and L{exec_command} is called (or C{invoke_shell}
with no pty), output to stderr will not show up through the L{recv}
and L{recv_ready} calls. You will have to use L{recv_stderr} and
L{recv_stderr_ready} to get stderr output.
If this is C{True}, data will never show up via L{recv_stderr} or
L{recv_stderr_ready}.
@param combine: C{True} if stderr output should be combined into
stdout on this channel.
@type combine: bool
@return: previous setting.
@rtype: bool
@since: 1.1
"""
old = self.combine_stderr
self.combine_stderr = combine
return old
### socket API ### socket API
@ -389,8 +424,8 @@ class Channel (object):
@note: This method doesn't work if you've called L{fileno}. @note: This method doesn't work if you've called L{fileno}.
""" """
self.lock.acquire()
try: try:
self.lock.acquire()
if len(self.in_buffer) == 0: if len(self.in_buffer) == 0:
return False return False
return True return True
@ -413,8 +448,8 @@ class Channel (object):
L{settimeout}. L{settimeout}.
""" """
out = '' out = ''
self.lock.acquire()
try: try:
self.lock.acquire()
if self.pipe_rfd != None: if self.pipe_rfd != None:
# use the pipe # use the pipe
return self._read_pipe(nbytes) return self._read_pipe(nbytes)
@ -445,6 +480,72 @@ class Channel (object):
self.lock.release() self.lock.release()
return out return out
def recv_stderr_ready(self):
"""
Returns true if data is buffered and ready to be read from this
channel's stderr stream. Only channels using L{exec_command} or
L{invoke_shell} without a pty will ever have data on the stderr
stream.
@return: C{True} if a L{recv_stderr} call on this channel would
immediately return at least one byte; C{False} otherwise.
@rtype: boolean
"""
self.lock.acquire()
try:
if len(self.in_stderr_buffer) == 0:
return False
return True
finally:
self.lock.release()
def recv_stderr(self, nbytes):
"""
Receive data from the channel's stderr stream. Only channels using
L{exec_command} or L{invoke_shell} without a pty will ever have data
on the stderr stream. The return value is a string representing the
data received. The maximum amount of data to be received at once is
specified by C{nbytes}. If a string of length zero is returned, the
channel stream has closed.
@param nbytes: maximum number of bytes to read.
@type nbytes: int
@return: data.
@rtype: str
@raise socket.timeout: if no data is ready before the timeout set by
L{settimeout}.
"""
out = ''
self.lock.acquire()
try:
if len(self.in_stderr_buffer) == 0:
if self.closed or self.eof_received:
return out
# should we block?
if self.timeout == 0.0:
raise socket.timeout()
# loop here in case we get woken up but a different thread has grabbed everything in the buffer
timeout = self.timeout
while (len(self.in_stderr_buffer) == 0) and not self.closed and not self.eof_received:
then = time.time()
self.in_stderr_buffer_cv.wait(timeout)
if timeout != None:
timeout -= time.time() - then
if timeout <= 0.0:
raise socket.timeout()
# something in the buffer and we have the lock
if len(self.in_stderr_buffer) <= nbytes:
out = self.in_stderr_buffer
self.in_stderr_buffer = ''
else:
out = self.in_stderr_buffer[:nbytes]
self.in_stderr_buffer = self.in_stderr_buffer[nbytes:]
self._check_add_window(len(out))
finally:
self.lock.release()
return out
def send(self, s): def send(self, s):
""" """
Send data to the channel. Returns the number of bytes sent, or 0 if Send data to the channel. Returns the number of bytes sent, or 0 if
@ -539,6 +640,22 @@ class Channel (object):
""" """
return ChannelFile(*([self] + list(params))) return ChannelFile(*([self] + list(params)))
def makefile_stderr(self, *params):
"""
Return a file-like object associated with this channel's stderr
stream. Only channels using L{exec_command} or L{invoke_shell}
without a pty will ever have data on the stderr stream.
The optional C{mode} and C{bufsize} arguments are interpreted the
same way as by the built-in C{file()} function in python, except that
of course it makes no sense to open this file in any mode other than
for reading.
@return: object which can be used for python file I/O.
@rtype: L{ChannelFile}
"""
return ChannelStderrFile(*([self] + list(params)))
def fileno(self): def fileno(self):
""" """
Returns an OS-level file descriptor which can be used for polling and Returns an OS-level file descriptor which can be used for polling and
@ -624,9 +741,13 @@ class Channel (object):
self.close() self.close()
def _feed(self, m): def _feed(self, m):
s = m.get_string() if type(m) is str:
# passed from _feed_extended
s = m
else:
s = m.get_string()
self.lock.acquire()
try: try:
self.lock.acquire()
if self.ultra_debug: if self.ultra_debug:
self._log(DEBUG, 'fed %d bytes' % len(s)) self._log(DEBUG, 'fed %d bytes' % len(s))
if self.pipe_wfd != None: if self.pipe_wfd != None:
@ -634,11 +755,26 @@ class Channel (object):
else: else:
self.in_buffer += s self.in_buffer += s
self.in_buffer_cv.notifyAll() self.in_buffer_cv.notifyAll()
if self.ultra_debug:
self._log(DEBUG, '(out from feed)')
finally: finally:
self.lock.release() self.lock.release()
def _feed_extended(self, m):
code = m.get_int()
s = m.get_string()
if code != 1:
self._log(ERROR, 'unknown extended_data type %d; discarding' % code)
return
if self.combine_stderr:
return self._feed(s)
self.lock.acquire()
try:
if self.ultra_debug:
self._log(DEBUG, 'fed %d stderr bytes' % len(s))
self.in_stderr_buffer += s
self.in_stderr_buffer_cv.notifyAll()
finally:
self.lock.release()
def _window_adjust(self, m): def _window_adjust(self, m):
nbytes = m.get_int() nbytes = m.get_int()
try: try:
@ -707,11 +843,12 @@ class Channel (object):
self.transport._send_user_message(m) self.transport._send_user_message(m)
def _handle_eof(self, m): def _handle_eof(self, m):
self.lock.acquire()
try: try:
self.lock.acquire()
if not self.eof_received: if not self.eof_received:
self.eof_received = 1 self.eof_received = 1
self.in_buffer_cv.notifyAll() self.in_buffer_cv.notifyAll()
self.in_stderr_buffer_cv.notifyAll()
if self.pipe_wfd != None: if self.pipe_wfd != None:
os.close(self.pipe_wfd) os.close(self.pipe_wfd)
self.pipe_wfd = None self.pipe_wfd = None
@ -741,6 +878,7 @@ class Channel (object):
# you are holding the lock. # you are holding the lock.
self.closed = True self.closed = True
self.in_buffer_cv.notifyAll() self.in_buffer_cv.notifyAll()
self.in_stderr_buffer_cv.notifyAll()
self.out_buffer_cv.notifyAll() self.out_buffer_cv.notifyAll()
def _send_eof(self): def _send_eof(self):
@ -893,4 +1031,12 @@ class ChannelFile (BufferedFile):
return len(data) return len(data)
class ChannelStderrFile (ChannelFile):
def __init__(self, channel, mode = 'r', bufsize = -1):
ChannelFile.__init__(self, channel, mode, bufsize)
def _read(self, size):
return self.channel.recv_stderr(size)
# vim: set shiftwidth=4 expandtab : # vim: set shiftwidth=4 expandtab :