1#! /usr/bin/env python
2
3"""
4    start socket based minimal readline exec server
5
6    it can exeuted in 2 modes of operation
7
8    1. as normal script, that listens for new connections
9
10    2. via existing_gateway.remote_exec (as imported module)
11
12"""
13# this part of the program only executes on the server side
14#
15
16import sys
17import os
18
19progname = 'socket_readline_exec_server-1.2'
20
21
22def get_fcntl():
23    try:
24        import fcntl
25    except ImportError:
26        fcntl = None
27    return fcntl
28
29fcntl = get_fcntl()
30
31debug = 0
32
33if debug:  # and not os.isatty(sys.stdin.fileno())
34    f = open('/tmp/execnet-socket-pyout.log', 'w')
35    old = sys.stdout, sys.stderr
36    sys.stdout = sys.stderr = f
37
38
39def print_(*args):
40    print(" ".join(str(arg) for arg in args))
41
42if sys.version_info > (3, 0):
43    exec("""def exec_(source, locs):
44    exec(source, locs)""")
45else:
46    exec("""def exec_(source, locs):
47    exec source in locs""")
48
49
50def exec_from_one_connection(serversock):
51    print_(progname, 'Entering Accept loop', serversock.getsockname())
52    clientsock, address = serversock.accept()
53    print_(progname, 'got new connection from %s %s' % address)
54    clientfile = clientsock.makefile('rb')
55    print_("reading line")
56    # rstrip so that we can use \r\n for telnet testing
57    source = clientfile.readline().rstrip()
58    clientfile.close()
59    g = {
60        'clientsock': clientsock,
61        'address': address,
62        'execmodel': execmodel,
63    }
64    source = eval(source)
65    if source:
66        co = compile(source+'\n', source, 'exec')
67        print_(progname, 'compiled source, executing')
68        try:
69            exec_(co, g)  # noqa
70        finally:
71            print_(progname, 'finished executing code')
72            # background thread might hold a reference to this (!?)
73            # clientsock.close()
74
75
76def bind_and_listen(hostport, execmodel):
77    socket = execmodel.socket
78    if isinstance(hostport, str):
79        host, port = hostport.split(':')
80        hostport = (host, int(port))
81    serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
82    # set close-on-exec
83    if hasattr(fcntl, 'FD_CLOEXEC'):
84        old = fcntl.fcntl(serversock.fileno(), fcntl.F_GETFD)
85        fcntl.fcntl(serversock.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
86    # allow the address to be re-used in a reasonable amount of time
87    if os.name == 'posix' and sys.platform != 'cygwin':
88        serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
89
90    serversock.bind(hostport)
91    serversock.listen(5)
92    return serversock
93
94
95def startserver(serversock, loop=False):
96    try:
97        while 1:
98            try:
99                exec_from_one_connection(serversock)
100            except (KeyboardInterrupt, SystemExit):
101                raise
102            except:
103                if debug:
104                    import traceback
105                    traceback.print_exc()
106                else:
107                    excinfo = sys.exc_info()
108                    print_("got exception", excinfo[1])
109            if not loop:
110                break
111    finally:
112        print_("leaving socketserver execloop")
113        serversock.shutdown(2)
114
115if __name__ == '__main__':
116    import sys
117    if len(sys.argv) > 1:
118        hostport = sys.argv[1]
119    else:
120        hostport = ':8888'
121    from execnet.gateway_base import get_execmodel
122    execmodel = get_execmodel("thread")
123    serversock = bind_and_listen(hostport, execmodel)
124    startserver(serversock, loop=False)
125elif __name__ == '__channelexec__':
126    chan = globals()['channel']
127    execmodel = chan.gateway.execmodel
128    bindname = chan.receive()
129    sock = bind_and_listen(bindname, execmodel)
130    port = sock.getsockname()
131    chan.send(port)
132    startserver(sock)
133