summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-12-10 08:25:28 +0000
committerRobey Pointer <robey@lag.net>2004-12-10 08:25:28 +0000
commit37892fc0c75bcdc8304eeb7d102334b30ac0f85d (patch)
tree0335cc6a9dfc44b89ef3e9275af2da33c51cfb4d
parentfb5493472615afd1fb41fc210185d06923dcb6fb (diff)
[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.
-rw-r--r--paramiko/channel.py172
1 files changed, 159 insertions, 13 deletions
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 202f89a3..8099c66a 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -72,20 +72,23 @@ class Channel (object):
self.eof_received = 0
self.eof_sent = 0
self.in_buffer = ''
+ self.in_stderr_buffer = ''
self.timeout = None
self.closed = False
self.ultra_debug = False
self.lock = threading.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.name = str(chanid)
self.logger = logging.getLogger('paramiko.chan.' + str(chanid))
self.pipe_rfd = self.pipe_wfd = None
self.event = threading.Event()
+ self.combine_stderr = False
def __repr__(self):
"""
- Returns a string representation of this object, for debugging.
+ Return a string representation of this object, for debugging.
@rtype: str
"""
@@ -108,7 +111,9 @@ class Channel (object):
"""
Request a pseudo-terminal from the server. This is usually used right
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'}).
@type term: str
@@ -144,8 +149,12 @@ class Channel (object):
def invoke_shell(self):
"""
Request an interactive shell session on this channel. If the server
- allows it, the channel will then be directly connected to the stdin
- and stdout of the shell.
+ allows it, the channel will then be directly connected to the stdin,
+ 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.
@rtype: bool
@@ -169,8 +178,8 @@ class Channel (object):
def exec_command(self, command):
"""
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
- being executed.
+ will then be directly connected to the stdin, stdout, and stderr of
+ the command being executed.
@param command: a shell command to execute.
@type command: str
@@ -296,6 +305,32 @@ class Channel (object):
@since: ivysaur
"""
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
@@ -389,8 +424,8 @@ class Channel (object):
@note: This method doesn't work if you've called L{fileno}.
"""
+ self.lock.acquire()
try:
- self.lock.acquire()
if len(self.in_buffer) == 0:
return False
return True
@@ -413,8 +448,8 @@ class Channel (object):
L{settimeout}.
"""
out = ''
+ self.lock.acquire()
try:
- self.lock.acquire()
if self.pipe_rfd != None:
# use the pipe
return self._read_pipe(nbytes)
@@ -445,6 +480,72 @@ class Channel (object):
self.lock.release()
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):
"""
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)))
+ 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):
"""
Returns an OS-level file descriptor which can be used for polling and
@@ -624,9 +741,13 @@ class Channel (object):
self.close()
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:
- self.lock.acquire()
if self.ultra_debug:
self._log(DEBUG, 'fed %d bytes' % len(s))
if self.pipe_wfd != None:
@@ -634,11 +755,26 @@ class Channel (object):
else:
self.in_buffer += s
self.in_buffer_cv.notifyAll()
- if self.ultra_debug:
- self._log(DEBUG, '(out from feed)')
finally:
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):
nbytes = m.get_int()
try:
@@ -707,11 +843,12 @@ class Channel (object):
self.transport._send_user_message(m)
def _handle_eof(self, m):
+ self.lock.acquire()
try:
- self.lock.acquire()
if not self.eof_received:
self.eof_received = 1
self.in_buffer_cv.notifyAll()
+ self.in_stderr_buffer_cv.notifyAll()
if self.pipe_wfd != None:
os.close(self.pipe_wfd)
self.pipe_wfd = None
@@ -741,6 +878,7 @@ class Channel (object):
# you are holding the lock.
self.closed = True
self.in_buffer_cv.notifyAll()
+ self.in_stderr_buffer_cv.notifyAll()
self.out_buffer_cv.notifyAll()
def _send_eof(self):
@@ -893,4 +1031,12 @@ class ChannelFile (BufferedFile):
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 :