def read(self): """ Blocking call which reads in command line input Not thread safe """ # Initialize new read self.line = [] self.insert_pos = 0 self.print_command() # Timings last_up = time.time() last_down = time.time() # Interaction loop last_char = None c = '' while True: last_char = c r, w, e = select([self._extra_stdin, self.stdin], [], []) # Receive stream from monitor if self._extra_stdin in r: self.stdout.write('\r') # Move cursor to the left self.stdout.write('\x1b[K') # Erase until the end of line self.stdout.write(self._extra_stdin.read(4096)) self.stdout.write('\r\n') # Clear line self.print_command() if self.stdin in r: c = self.stdin.read(1) # self.stdout.write(' %i ' % ord(c)) # self.stdout.flush() # continue # Contrel-A if c == '\x01': self.home() # Control-B elif c == '\x02': self.cursor_left() # Control-C elif c == '\x03': self.ctrl_c() # Control-D elif c == '\x04': self.exit() # Contrel-E elif c == '\x05': self.end() # Control-F elif c == '\x06': self.cursor_right() # Control-K elif c == '\x0b': self.delete_until_end() # Control-L elif c == '\x0c': self.clear() # Control-N elif c == '\x0e': # 14 self.history_forward() # Control-P elif c == '\x10': # 16 self.history_back() # Control-R elif c == '\x12': # 18 self.stdout.write('\r\nSorry, reverse search is not supported.\r\n') self.print_command() # Enter elif c in ('\r', '\n'): # Depending on the client \n or \r is sent. # Restore terminal self.stdout.write('\r\n') self.stdout.flush() # Return result return ''.join(self.line) # Tab completion elif c == '\t': # Double tab press: print all completions and show new prompt. if last_char == '\t': all_completions = self.complete(True) if all_completions: self.print_all_completions(all_completions) self.print_command() else: # Call tab completion append = self.complete() self.line = self.line[:self.insert_pos] for a in append: self.line.append(a) self.insert_pos += 1 self.print_command() # Backspace elif c in ('\x08', '\x7f'): # (127) Backspace self.backspace() # Escape characters for cursor movement elif c == '\x1b': # (27) Escape # When no other characters are followed immediately after the # escape character, we consider it an ESC key press if not select( [self.stdin], [], [], 0)[0]: self.vi_navigation = True c = '' else: c = self.stdin.read(1) if c in ('[', 'O'): # (91, 68) c = self.stdin.read(1) # Cursor to left if c == 'D': self.cursor_left() # Cursor to right elif c == 'C': self.cursor_right() # Cursor up: elif c == 'A': if time.time() - last_up > 0.01: self.history_back() last_up = time.time() # NOTE: When the scrolling events occur too fast, # we'll skip them, because mouse scrolling can generate # multiple up or down events, and we need only # one. # Cursor down: elif c == 'B': if time.time() - last_down > 0.01: self.history_forward() last_down = time.time() # Delete key: esc[3~ elif c == '3': c = self.stdin.read(1) if c == '~': self.delete() # xrvt sends esc[7~ for home # some others send esc[1~ (tmux) elif c in ('1', '7'): c = self.stdin.read(1) if c == '~': self.home() # xrvt sends esc[8~ for end # some others send esc[4~ (tmux) elif c in ('4', '8'): c = self.stdin.read(1) if c == '~': self.end() # Home (xterm) if c == 'H': self.home() # End (xterm) elif c == 'F': self.end() # Insert character else: if self.vi_navigation: if c == 'h': # Move left self.cursor_left() elif c == 'l': # Move right self.cursor_right() elif c == 'I': # Home self.vi_navigation = False self.home() elif c == 'x': # Delete self.delete() elif c == 'A': # Home self.vi_navigation = False self.end() elif c == 'i': # Back to insert mode self.vi_navigation = False elif c == 'a': # Back to insert mode self.vi_navigation = False self.cursor_right() elif c == 'w': # Move word forward self.word_forwards() elif c == 'b': # Move word backwards self.word_backwards() elif c == 'D': # Delete until end self.delete_until_end() elif c == 'd': # Delete c = self.stdin.read(1) if c == 'w': # Delete word self.delete_word() # Only printable characters (space to tilde, or 32..126) elif c >= ' ' and c <= '~': # Note: correct handling of UTF-8 input, can be done # by using the codecs.getreader, but it's complex to # get it working right with the ANSI terminal escape # codes, and buffering, and we don't really need it # anyway. # # stdin_utf8 = codecs.getreader('utf-8')(sys.stdin) if self.insert_pos < len(self.line): self.line = self.line[:self.insert_pos] + [c] + self.line[self.insert_pos:] else: self.line.append(c) self.insert_pos += 1 self.print_command() self.stdout.flush()
def _posix_shell(self, pty, chan, raw=True, log_entry=None, initial_input=None): """ Create a loop which redirects sys.stdin/stdout into this channel. The loop ends when channel.recv() returns 0. Code inspired by the Paramiko interactive demo. """ result = [] password_sent = False # Make stdin non-blocking. (The select call will already # block for us, we want sys.stdin.read() to read as many # bytes as possible without blocking.) fdesc.setNonBlocking(pty.stdin) # Set terminal in raw mode if raw: context = raw_mode(pty.stdin) else: context = contextlib.nested() with context: try: chan.settimeout(0.0) # When initial input has been given, send this first if initial_input: time.sleep(0.2) # Wait a very short while for the channel to be initialized, before sending. chan.send(initial_input) reading_from_stdin = True # Read/write loop while True: # Don't wait for any input when an exit status code has been # set already. if chan.status_event.isSet(): break; if reading_from_stdin: r, w, e = select([pty.stdin, chan], [], []) else: r, w, e = select([chan], [], []) # Receive stream if chan in r: try: x = chan.recv(1024) # Received length 0 -> end of stream if len(x) == 0: break # Log received characters log_entry.log_io(x) # Write received characters to stdout and flush while True: try: pty.stdout.write(x) break except IOError, e: # Sometimes, when we have a lot of output, we get here: # IOError: [Errno 11] Resource temporarily unavailable # Just waiting a little, and retrying seems to work. # See also: deployer.run.socket_client for a similar issue. time.sleep(0.2) pty.stdout.flush() # Also remember received output. # We want to return what's written to stdout. result.append(x) # Do we need to send the sudo password? (It's when the # magic prompt has been detected in the stream) Note # that we only monitor the last part of 'result', it's # a bit fuzzy, but works. if not password_sent and self.magic_sudo_prompt in ''.join(result[-32:]): chan.send(self.password) chan.send('\n') password_sent = True except socket.timeout: pass # Send stream (one by one character) if pty.stdin in r: try: x = pty.stdin.read(1024) # We receive \n from stdin, but \r is required to # send. (Until now, the only place where the # difference became clear is in redis-cli, which # only accepts \r as confirmation.) x = x.replace('\n', '\r') except IOError, e: # What to do with IOError exceptions? # (we see what happens in the next select-call.) continue # Received length 0 # There's no more at stdin to read. if len(x) == 0: # However, we should go on processing the input # from the remote end, until the process finishes # there (because it was done or processed Ctrl-C or # Ctrl-D/EOF.) # # The end of the input stream happens when we are # using StringIO at the client side, and we're not # attached to a real pseudo terminal. (For # unit-testing, or background commands.) reading_from_stdin = False continue # Write to channel chan.send(x) # Not sure about this. Sometimes, when pasting large data # in the command line, the many concecutive read or write # commands will make Paramiko hang somehow... (This was # the case, when still using a blocking pty.stdin.read(1) # instead of a non-blocking readmany. time.sleep(0.01)
def _posix_shell(self, chan, raw=True, initial_input=None): """ Create a loop which redirects sys.stdin/stdout into this channel. The loop ends when channel.recv() returns 0. Code inspired by the Paramiko interactive demo. """ result = [] password_sent = False # Set terminal in raw mode if raw: context = raw_mode(self.pty.stdin) else: context = contextlib.nested() assert self.pty.set_ssh_channel_size with context: # Make channel non blocking. chan.settimeout(0.0) # When initial input has been given, send this first if initial_input: time.sleep( 0.2 ) # Wait a very short while for the channel to be initialized, before sending. chan.send(initial_input) reading_from_stdin = True # Read/write loop while True: # Don't wait for any input when an exit status code has been # set already. (But still wait for the output to finish.) if chan.status_event.isSet(): reading_from_stdin = False # When the channel is closed, and there's nothing to read # anymore. We can return what we got from Paramiko. (Not # sure why this happens. Most of the time, select() still # returns and chan.recv() returns an empty string, but when # read_ready is False, select() doesn't return anymore.) if chan.closed and not chan.in_buffer.read_ready(): break channels = [self.pty.stdin, chan ] if reading_from_stdin else [chan] r, w, e = select(channels, [], [], 1) # Note the select-timeout. That is required in order to # check for the status_event every second. # Receive stream if chan in r: try: x = chan.recv(1024) # Received length 0 -> end of stream if len(x) == 0: break # Write received characters to stdout and flush while True: try: self.pty.stdout.write(x) break except IOError as e: # Sometimes, when we have a lot of output, we get here: # IOError: [Errno 11] Resource temporarily unavailable # Just waiting a little, and retrying seems to work. # See also: deployer.run.socket_client for a similar issue. time.sleep(0.2) self.pty.stdout.flush() # Also remember received output. # We want to return what's written to stdout. result.append(x) # Do we need to send the sudo password? (It's when the # magic prompt has been detected in the stream) Note # that we only monitor the last part of 'result', it's # a bit fuzzy, but works. if not password_sent and self.magic_sudo_prompt in ''.join( result[-32:]): chan.send(self.password) chan.send('\n') password_sent = True except socket.timeout: pass # Send stream (one by one character) # (use 'elif', read stdin only when there is no more output to be received.) elif self.pty.stdin in r: try: # Make stdin non-blocking. (The select call already # blocked for us, we want sys.stdin.read() to read # as many bytes as possible without blocking.) try: fdesc.setNonBlocking(self.pty.stdin) x = self.pty.stdin.read(1024) finally: # Set stdin blocking again # (Writing works better in blocking mode. # Especially OS X seems to be very sensitive if we # write lange amounts [>1000 bytes] nonblocking to # stdout. That causes a lot of IOErrors.) fdesc.setBlocking(self.pty.stdin) # We receive \n from stdin, but \r is required to # send. (Until now, the only place where the # difference became clear is in redis-cli, which # only accepts \r as confirmation.) x = x.replace('\n', '\r') except IOError as e: # What to do with IOError exceptions? # (we see what happens in the next select-call.) continue # Received length 0 # There's no more at stdin to read. if len(x) == 0: # However, we should go on processing the input # from the remote end, until the process finishes # there (because it was done or processed Ctrl-C or # Ctrl-D/EOF.) # # The end of the input stream happens when we are # using StringIO at the client side, and we're not # attached to a real pseudo terminal. (For # unit-testing, or background commands.) reading_from_stdin = False continue # Write to channel chan.send(x) # Not sure about this. Sometimes, when pasting large data # in the command line, the many concecutive read or write # commands will make Paramiko hang somehow... (This was # the case, when still using a blocking pty.stdin.read(1) # instead of a non-blocking readmany. time.sleep(0.01) return ''.join(result)
logging.debug('Running wrapped command "%s" interactive' % wrapped_command) chan.exec_command(wrapped_command) else: chan.exec_command(self._wrap_command(command, context, sandbox)) if interactive: # Pty receive/send loop result = self._posix_shell(pty, chan, log_entry=log_entry, initial_input=initial_input) else: # Read loop. result = [] while True: # Before calling recv, call select to make sure # the channel is ready to be read. (Trick for # getting the SIGCHLD pipe of Localhost to work.) r, w, e = select([chan], [], []) if chan in r: # Blocking call. Returns when data has been received or at # the end of the channel stream. try: data = chan.recv(1024) except IOError: # In case of localhost: application terminated, # caught in SIGCHLD, and closed slave PTY break if data: result += [data] else: break
def read(self): """ Blocking call which reads in command line input Not thread safe """ # Initialize new read self.line = [] self.insert_pos = 0 self.print_command() # Timings last_up = time.time() last_down = time.time() # Interaction loop last_char = None c = '' while True: last_char = c r, w, e = select([self._extra_stdin, self.stdin], [], []) # Receive stream from monitor if self._extra_stdin in r: self.stdout.write('\r') # Move cursor to the left self.stdout.write('\x1b[K') # Erase until the end of line self.stdout.write(self._extra_stdin.read(4096)) self.stdout.write('\r\n') # Clear line self.print_command() if self.stdin in r: c = self.stdin.read(1) # self.stdout.write(' %i ' % ord(c)) # self.stdout.flush() # continue # Contrel-A if c == '\x01': self.home() # Control-B elif c == '\x02': self.cursor_left() # Control-C elif c == '\x03': self.ctrl_c() # Control-D elif c == '\x04': self.exit() # Contrel-E elif c == '\x05': self.end() # Control-F elif c == '\x06': self.cursor_right() # Control-K elif c == '\x0b': self.delete_until_end() # Control-L elif c == '\x0c': self.clear() # Control-N elif c == '\x0e': # 14 self.history_forward() # Control-P elif c == '\x10': # 16 self.history_back() # Control-R elif c == '\x12': # 18 self.stdout.write( '\r\nSorry, reverse search is not supported.\r\n') self.print_command() # Enter elif c in ('\r', '\n'): # Depending on the client \n or \r is sent. # Restore terminal self.stdout.write('\r\n') self.stdout.flush() # Return result return ''.join(self.line) # Tab completion elif c == '\t': # Double tab press: print all completions and show new prompt. if last_char == '\t': all_completions = self.complete(True) if all_completions: self.print_all_completions(all_completions) self.print_command() else: # Call tab completion append = self.complete() self.line = self.line[:self.insert_pos] for a in append: self.line.append(a) self.insert_pos += 1 self.print_command() # Backspace elif c in ('\x08', '\x7f'): # (127) Backspace self.backspace() # Escape characters for cursor movement elif c == '\x1b': # (27) Escape # When no other characters are followed immediately after the # escape character, we consider it an ESC key press if not select([self.stdin], [], [], 0)[0]: self.vi_navigation = True c = '' else: c = self.stdin.read(1) if c in ('[', 'O'): # (91, 68) c = self.stdin.read(1) # Cursor to left if c == 'D': self.cursor_left() # Cursor to right elif c == 'C': self.cursor_right() # Cursor up: elif c == 'A': if time.time() - last_up > 0.01: self.history_back() last_up = time.time() # NOTE: When the scrolling events occur too fast, # we'll skip them, because mouse scrolling can generate # multiple up or down events, and we need only # one. # Cursor down: elif c == 'B': if time.time() - last_down > 0.01: self.history_forward() last_down = time.time() # Delete key: esc[3~ elif c == '3': c = self.stdin.read(1) if c == '~': self.delete() # xrvt sends esc[7~ for home # some others send esc[1~ (tmux) elif c in ('1', '7'): c = self.stdin.read(1) if c == '~': self.home() # xrvt sends esc[8~ for end # some others send esc[4~ (tmux) elif c in ('4', '8'): c = self.stdin.read(1) if c == '~': self.end() # Home (xterm) if c == 'H': self.home() # End (xterm) elif c == 'F': self.end() # Insert character else: if self.vi_navigation: if c == 'h': # Move left self.cursor_left() elif c == 'l': # Move right self.cursor_right() elif c == 'I': # Home self.vi_navigation = False self.home() elif c == 'x': # Delete self.delete() elif c == 'A': # Home self.vi_navigation = False self.end() elif c == 'i': # Back to insert mode self.vi_navigation = False elif c == 'a': # Back to insert mode self.vi_navigation = False self.cursor_right() elif c == 'w': # Move word forward self.word_forwards() elif c == 'b': # Move word backwards self.word_backwards() elif c == 'D': # Delete until end self.delete_until_end() elif c == 'd': # Delete c = self.stdin.read(1) if c == 'w': # Delete word self.delete_word() # Only printable characters (space to tilde, or 32..126) elif c >= ' ' and c <= '~': # Note: correct handling of UTF-8 input, can be done # by using the codecs.getreader, but it's complex to # get it working right with the ANSI terminal escape # codes, and buffering, and we don't really need it # anyway. # # stdin_utf8 = codecs.getreader('utf-8')(sys.stdin) if self.insert_pos < len(self.line): self.line = self.line[:self.insert_pos] + [ c ] + self.line[self.insert_pos:] else: self.line.append(c) self.insert_pos += 1 self.print_command() self.stdout.flush()