[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:
parent
fb54934726
commit
37892fc0c7
|
@ -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 :
|
||||||
|
|
Loading…
Reference in New Issue