Beispiel #1
0
    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()
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #4
0
                    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
Beispiel #5
0
    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()