rpathsync / epdb

An Extend Python debugger, based on the python debugger pdb. Epdb adds readline support amongst other features to pdb.

Clone this repository (size: 68.9 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/rpathsync/epdb/
commit 40: de3c89d2fecf
parent 39: d4065b9aaf2c
branch: default
add telnetserver + telnet client
David Christian
3 years ago

Changed (Δ13.6 KB):

raw changeset »

epdb/epdb.py (89 lines added, 14 lines removed)

epdb/telnetclient.py (83 lines added, 0 lines removed)

epdb/telnetserver.py (261 lines added, 0 lines removed)

Up to file-list epdb/epdb.py:

1
#!/usr/bin/python2.4
1
2
#
2
3
# Copyright (c) 2004-2005 rPath, Inc.
3
4
#
@@ -24,12 +25,21 @@ except ImportError:
24
25
    hasReadline = False
25
26
else:
26
27
    hasReadline = True
28
import signal
27
29
import socket
28
30
import string
29
31
import sys
30
32
import tempfile
31
33
import traceback
32
34
35
try:
36
    sys.path.append('/home/dbc/rpl/hg/telnet')
37
    import telnetserver
38
    import telnetclient
39
    hasTelnet = True
40
except ImportError:
41
    hasTelnet = False
42
33
43
from pdb import _saferepr
34
44
35
45
class Epdb(pdb.Pdb):
@@ -47,8 +57,8 @@ class Epdb(pdb.Pdb):
47
57
    # used to track the number of times a set_trace has been seen
48
58
    trace_counts = {'default' : [ True, 0 ]}
49
59
50
51
60
    def __init__(self):
61
        self._server = None
52
62
        self._exc_type = None
53
63
        self._exc_msg = None
54
64
        self._tb = None
@@ -99,6 +109,38 @@ class Epdb(pdb.Pdb):
99
109
            else:
100
110
                readline.clear_history()
101
111
112
    if hasTelnet:
113
        # telnet server support.
114
        # if enabled, you can serve a epdb session.
115
        def serve(self, port=8080):
116
            print 'Serving on port %s' % port
117
            self._server = telnetserver.InvertedTelnetServer(('', port))
118
            self._server.handle_request()
119
            self.set_trace(skip=2)
120
121
        def serve_post_mortem(self, t, exc_type=None, exc_msg=None, port=8080):
122
            print 'Serving on port %s' % port
123
            self._server = telnetserver.InvertedTelnetServer(('', port))
124
            self._server.handle_request()
125
            self.post_mortem(t, exc_type, exc_msg, port)
126
127
        def do_detach(self, arg):
128
            if self.server:
129
                print ('Leaving process in debug state - use "close" to'
130
                       ' stop debug session')
131
                self._server.close_request()
132
                self._server.handle_request()
133
            else:
134
                print "Not attached via telnet"
135
136
        def do_close(self, arg):
137
            if self.server:
138
                print 'Ending epdb session - use "detach" to stop serving'
139
                self._server.close_request()
140
                return True
141
            else:
142
                print "Not attached via telnet"
143
102
144
    def do_savestack(self, path):
103
145
        if 'stack' in self.__dict__:
104
146
            # when we're saving we always 
@@ -304,8 +346,6 @@ class Epdb(pdb.Pdb):
304
346
            return sys.modules[origFileName].__file__
305
347
        return None
306
348
307
308
309
349
    def default(self, line):
310
350
        if line[0] == '!': line = line[1:]
311
351
        if self.handle_directive(line):
@@ -327,7 +367,6 @@ class Epdb(pdb.Pdb):
327
367
            return pdb.Pdb.default(self, origLine)
328
368
        finally:
329
369
            self.read_history()
330
            
331
370
332
371
    def multiline(self, firstline=''):
333
372
        full_input = []
@@ -669,17 +708,26 @@ class Epdb(pdb.Pdb):
669
708
        self.save_history(restoreOldHistory=True)
670
709
        self.restore_input_output()
671
710
711
    def post_mortem(self, t, exc_type, exc_msg):
712
        p._exc_type = exc_type
713
        p._exc_msg = exc_msg
714
        p._tb = t
715
        p.reset()
716
        while t.tb_next is not None:
717
            t = t.tb_next
718
        p.interaction(t.tb_frame, t)
719
672
720
    def switch_input_output(self):
673
721
        self.switch_stdout()
674
722
        self.switch_stdin()
675
723
        self.switch_pgid()
676
724
677
725
    def restore_input_output(self):
678
        if not self.__old_stdout is None:
726
        if self.__old_stdout is not None:
679
727
            sys.stdout.flush()
680
728
            # now we reset stdout to be the whatever it was before
681
729
            sys.stdout = self.__old_stdout
682
        if not self.__old_stdin is None:
730
        if self.__old_stdin is not None:
683
731
            sys.stdin = self.__old_stdin
684
732
        if self.__old_pgid is not None:
685
733
	    os.setpgid(0, self.__old_pgid)
@@ -903,15 +951,19 @@ def set_trace(marker='default'):
903
951
904
952
st = set_trace
905
953
954
if hasTelnet:
955
    def serve(port=8080):
956
        Epdb().serve(port)
957
    
958
    def serve_post_mortem(t, exc_type=None, exc_msg=None, port=8080):
959
        Epdb().serve_post_mortem(t, exc_type, exc_msg, port)
960
961
    def connect(host='localhost', port=8080):
962
        t = telnetclient.TelnetClient(host, port)
963
        t.interact()
964
906
965
def post_mortem(t, exc_type=None, exc_msg=None):
907
    p = Epdb()
908
    p._exc_type = exc_type
909
    p._exc_msg = exc_msg
910
    p._tb = t
911
    p.reset()
912
    while t.tb_next is not None:
913
        t = t.tb_next
914
    p.interaction(t.tb_frame, t)
966
    Epdb().post_mortem(t, exc_type=None, exc_msg=None)
915
967
916
968
def matchFileOnDirPath(curpath, pathdir):
917
969
    """Find match for a file by slicing away its directory elements
@@ -985,3 +1037,26 @@ def _removeQuoteSet(line, quote1, quote2
985
1037
        secondPoint += firstPoint
986
1038
        line = line[:firstPoint] + line[(secondPoint+2*ln):]
987
1039
1040
def main(argv):
1041
    if len(argv) == 1:
1042
        set_trace()
1043
        return
1044
    command = argv[1]
1045
    if command == 'connect':
1046
        if len(argv) > 2:
1047
            host = argv[2]
1048
        else:
1049
            host = 'localhost'
1050
        if len(argv) > 3:
1051
            port = int(argv[3])
1052
        else:
1053
            port = 8080
1054
        connect(host=host, port=port)
1055
    else:
1056
        print "usage: connect [host] [port]"
1057
        return 1
1058
    return 0
1059
1060
if __name__ == '__main__':
1061
    sys.exit(main(sys.argv))
1062

Up to file-list epdb/telnetclient.py:

1
#!/usr/bin/python
2
import errno
3
import fcntl
4
import os
5
import select
6
import signal
7
import struct
8
import sys
9
import telnetlib
10
import termios
11
12
from telnetlib import IAC, IP, SB, SE, NAWS
13
14
def getTerminalSize():
15
    s = struct.pack('HHHH', 0, 0, 0, 0)
16
    result = fcntl.ioctl(sys.stdin.fileno(), termios.TIOCGWINSZ, s)
17
    rows, cols = struct.unpack('HHHH', result)[0:2]
18
    return rows, cols
19
20
class TelnetClient(telnetlib.Telnet):
21
    def __init__(self, *args, **kw):
22
        telnetlib.Telnet.__init__(self, *args, **kw)
23
        signal.signal(signal.SIGINT, self.ctrl_c)
24
        signal.signal(signal.SIGWINCH, self.sigwinch)
25
        self.oldTerm = None
26
        self.oldFlags = None
27
28
    def set_raw_mode(self):
29
        fd = sys.stdin.fileno()
30
        self.oldTerm = termios.tcgetattr(fd)
31
        newattr = termios.tcgetattr(fd)
32
        newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
33
        termios.tcsetattr(fd, termios.TCSANOW, newattr)
34
        self.oldFlags = fcntl.fcntl(fd, fcntl.F_GETFL)
35
        fcntl.fcntl(fd, fcntl.F_SETFL, self.oldFlags | os.O_NONBLOCK)
36
37
    def restore_terminal(self):
38
        fd = sys.stdin.fileno()
39
        if self.oldTerm:
40
            termios.tcsetattr(fd, termios.TCSAFLUSH, self.oldTerm)
41
        if self.oldFlags:
42
            fcntl.fcntl(fd, fcntl.F_SETFL, self.oldFlags)
43
44
    def ctrl_c(self, int, tb):
45
        self.sock.sendall(IAC + IP)
46
47
    def sigwinch(self, int, tb):
48
        self.updateTerminalSize()
49
50
    def updateTerminalSize(self):
51
        rows, cols = getTerminalSize()
52
        self.sock.sendall(IAC + SB + NAWS + chr(cols) + chr(rows) + IAC + SE)
53
54
    def interact(self):
55
        self.set_raw_mode()
56
        self.updateTerminalSize()
57
        try:
58
            while 1:
59
                try:
60
                    rfd, wfd, xfd = select.select([self, sys.stdin], [], [])
61
                except select.error, err:
62
                    if err.args[0] != errno.EINTR: # ignore interrupted select
63
                        raise
64
                if self in rfd:
65
                    try:
66
                        text = self.read_eager()
67
                    except EOFError:
68
                        print '*** Connection closed by remote host ***'
69
                        break
70
                    if text:
71
                        sys.stdout.write(text)
72
                        sys.stdout.flush()
73
                if sys.stdin in rfd:
74
                    line = sys.stdin.read(4096)
75
                    if not line:
76
                        break
77
                    self.write(line)
78
        finally:
79
            self.restore_terminal()
80
81
if __name__ == '__main__':
82
    t = TelnetClient('localhost', 8000)
83
    t.interact()

Up to file-list epdb/telnetserver.py:

1
#!/usr/bin/python
2
# Copyright (c) 2005-2006 rPath, Inc.
3
#
4
# This program is distributed under the terms of the Common Public License,
5
# version 1.0. A copy of this license should have been distributed with this
6
# source file in a file called LICENSE. If it is not present, the license
7
# is always available at http://www.opensource.org/licenses/cpl.php.
8
#
9
# This program is distributed in the hope that it will be useful, but
10
# without any warranty; without even the implied warranty of merchantability
11
# or fitness for a particular purpose. See the Common Public License for
12
# full details.
13
#
14
"""
15
Telnet Server implementation.
16
17
Based on telnetlib telnet client - reads in and parses telnet protocol
18
from the socket, understands window change requests and interrupt requests. 
19
(IP and NAWS).
20
21
This server does _NOT_ do LINEMODE, instead it is character based.  This means
22
to talk to this server using the standard telnet client, you'll need to first
23
type "CTRL-] mode char\n"
24
"""
25
from SocketServer import TCPServer, BaseRequestHandler
26
import fcntl
27
import os
28
import pty
29
import select
30
import signal
31
import socket
32
import struct
33
import sys
34
import telnetlib
35
import termios
36
import tty
37
from telnetlib import IAC, IP, SB, SE, DO, DONT, WILL, WONT, TM, LINEMODE, NAWS
38
39
class TelnetServerProtocolHandler(telnetlib.Telnet):
40
    """
41
        Code that actually understands telnet protocol.
42
        Accepts telnet-coded input from the socket and passes on that
43
        information to local, which should be the master for a pty controlled
44
        process.
45
    """
46
    def __init__(self, socket, local):
47
        telnetlib.Telnet.__init__(self)
48
        self.sock = socket
49
        self.remote = self.sock.fileno()
50
        self.local = local
51
        self.set_option_negotiation_callback(self.process_IAC)
52
53
    def process_IAC(self, sock, cmd, option):
54
        """
55
            Read in and parse IAC commands as passed by telnetlib.
56
57
            SB/SE commands are stored in sbdataq, and passed in w/ a command
58
            of SE.  
59
        """
60
        if cmd == DO:
61
            if option == TM: # timing mark - send WILL into outgoing stream
62
                os.write(self.remote, IAC + WILL + TM)
63
            else:
64
                pass
65
        elif cmd == IP:
66
            # interrupt process
67
            os.write(self.local, chr(ord('C') & 0x1F))
68
        elif cmd == SB:
69
            pass
70
        elif cmd == SE:
71
            option = self.sbdataq[0]
72
            if option == NAWS: # negotiate window size.
73
                cols = ord(self.sbdataq[1])
74
                rows = ord(self.sbdataq[2])
75
                s = struct.pack('HHHH', rows, cols, 0, 0)
76
                fcntl.ioctl(self.local, termios.TIOCSWINSZ, s)
77
        elif cmd == DONT:
78
            pass
79
        else:
80
            pass
81
82
    def handle(self):
83
        """
84
            Performs endless processing of socket input/output, passing
85
            cooked information onto the local process.
86
        """
87
        while True:
88
            toRead = select.select([self.local, self.remote], [], [], 0.1)[0]
89
            if self.local in toRead:
90
                data = os.read(self.local, 4096)
91
                self.sock.sendall(data)
92
                continue
93
            if self.remote in toRead or self.rawq:
94
                buf = self.read_eager()
95
                os.write(self.local, buf)
96
                continue
97
98
class TelnetRequestHandler(BaseRequestHandler):
99
    """
100
        Request handler that serves up a shell for users who connect.
101
        Derive from this class to change the execute() method to change how
102
        what command the request serves to the client.
103
    """
104
    command = '/bin/sh'
105
    args = ['/bin/sh']
106
107
    def setup(self):
108
        pass
109
110
    def handle(self):
111
        """
112
            Creates a child process that is fully controlled by this
113
            request handler, and serves data to and from it via the 
114
            protocol handler.
115
        """
116
        pid, fd = pty.fork()
117
        if pid:
118
            protocol = TelnetServerProtocolHandler(self.request, fd)
119
            protocol.handle()
120
        else:
121
            self.execute()
122
123
    def execute(self):
124
        try:
125
            os.execv(self.command, self.args)
126
        finally:
127
            os._exit(1)
128
129
    def finish(self):
130
        pass
131
132
133
class TelnetServer(TCPServer):
134
135
    allow_reuse_address = True
136
137
    def __init__(self, server_address=None, 
138
                 requestHandlerClass=TelnetRequestHandler):
139
        if not server_address:
140
            server_address = ('', 23)
141
        TCPServer.__init__(self, server_address, requestHandlerClass)
142
143
class TelnetServerForCommand(TelnetServer):
144
    def __init__(self, server_address=None, 
145
                 requestHandlerClass=TelnetRequestHandler, 
146
                 command=['/bin/sh']):
147
        class RequestHandler(requestHandlerClass):
148
            pass
149
        RequestHandler.command = command[0]
150
        RequestHandler.args = command
151
        TelnetServer.__init__(self, server_address, RequestHandler)
152
153
class InvertedTelnetRequestHandler(TelnetRequestHandler):
154
    def handle(self):
155
        masterFd, slaveFd = pty.openpty()
156
157
        signal.signal(signal.SIGTTOU, signal.SIG_IGN)
158
        pid = os.fork()
159
        if pid:
160
            os.close(masterFd)
161
            raise SocketConnected(slaveFd, pid)
162
            # make parent process the pty slave - the opposite of
163
            # pty.fork().  In this setup, the parent process continues
164
            # to act normally, while the child process performs the
165
            # logging.  This makes it simple to kill the logging process
166
            # when we are done with it and restore the parent process to
167
            # normal, unlogged operation.
168
        else:
169
            os.close(slaveFd)
170
            try:
171
                protocol = TelnetServerProtocolHandler(self.request, masterFd)
172
                protocol.handle()
173
            finally:
174
                os._exit(1)
175
176
class InvertedTelnetServer(TelnetServer):
177
    """
178
        Creates a telnet server that controls the stdin and stdout
179
        of the current process, instead of serving a subprocess.
180
181
        The telnet server can be closed at any time, and when it is
182
        input and output for the current process will be restored.
183
    """
184
    def __init__(self, server_address=None,
185
                 requestHandlerClass=InvertedTelnetRequestHandler):
186
        TelnetServer.__init__(self, server_address, requestHandlerClass)
187
        self.closed = True
188
        self.oldStdin = self.oldStdout = self.oldStderr = None
189
        self.oldTermios = None
190
191
    def handle_request(self):
192
        """
193
            Handle one request - serve current process to one connection.
194
195
            Use close_request() to disconnect this process.
196
        """
197
        try:
198
            request, client_address = self.get_request()
199
        except socket.error:
200
            return
201
        if self.verify_request(request, client_address):
202
            try:
203
                self.process_request(request, client_address)
204
            except SocketConnected, err:
205
                self._serve_process(err.slaveFd, err.serverPid)
206
                return
207
            except:
208
                self.handle_error(request, client_address)
209
                self.close_request(request)
210
211
    def _serve_process(self, slaveFd, serverPid):
212
        """
213
            Serves a process by connecting its outputs/inputs to the pty
214
            slaveFd.  serverPid is the process controlling the master fd
215
            that passes that output over the socket.
216
        """
217
        self.serverPid = serverPid
218
        if sys.stdin.isatty():
219
            self.oldTermios = termios.tcgetattr(sys.stdin.fileno())
220
        else:
221
            self.oldTermios = None
222
        self.oldStderr = os.dup(sys.stderr.fileno())
223
        self.oldStdout = os.dup(sys.stdout.fileno())
224
        self.oldStdin = os.dup(sys.stdin.fileno())
225
        os.dup2(slaveFd, 0)
226
        os.dup2(slaveFd, 1)
227
        os.dup2(slaveFd, 2)
228
        os.close(slaveFd)
229
        self.closed = False
230
231
    def close_request(self):
232
        if self.closed:
233
            pass
234
        self.closed = True
235
        # restore old terminal settings before quitting
236
        os.dup2(self.oldStdin, 0)
237
        os.dup2(self.oldStdout, 1)
238
        os.dup2(self.oldStderr, 2)
239
        if self.oldTermios is not None:
240
            termios.tcsetattr(0, termios.TCSADRAIN, self.oldTermios)
241
        os.close(self.oldStdin)
242
        os.close(self.oldStdout)
243
        os.close(self.oldStderr)
244
        os.waitpid(self.serverPid, 0)
245
246
class SocketConnected(Exception):
247
    """
248
        Control-Flow Exception raised when we have successfully connected 
249
        a socket.
250
251
        Used for IntertedTelnetServer
252
    """
253
    def __init__(self, slaveFd, serverPid):
254
        self.slaveFd = slaveFd
255
        self.serverPid = serverPid
256
257
258
if __name__ == '__main__':
259
    print 'serving on 8081....'
260
    t = TelnetServer(('', 8081))
261
    t.serve_forever()