~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: dcoles
  • Date: 2008-08-07 06:51:57 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:990
Consoleservice: Fixed a slightly confusing error message. This occurs when the 
python-console service either spits out rubbish or returns nothing at all 
(usually a forceful termination).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# usage:
 
4
#   python-console <port> <magic> [<working-dir>]
 
5
 
 
6
import cjson
 
7
import codeop
 
8
import md5
 
9
import os
 
10
import Queue
 
11
import signal
 
12
import socket
 
13
import sys
 
14
import traceback
 
15
from threading import Thread
 
16
 
 
17
import common.chat
 
18
 
 
19
class Interrupt(Exception):
 
20
    def __init__(self):
 
21
        Exception.__init__(self, "Interrupted!")
 
22
 
 
23
class ExpiryTimer(object):
 
24
    def __init__(self, idle):
 
25
        self.idle = idle
 
26
        signal.signal(signal.SIGALRM, self.timeout)
 
27
 
 
28
    def ping(self):
 
29
        signal.alarm(self.idle)
 
30
 
 
31
    def start(self, time):
 
32
        signal.alarm(time)
 
33
 
 
34
    def stop(self):
 
35
        self.ping()
 
36
 
 
37
    def timeout(self, signum, frame):
 
38
        sys.exit(1)
 
39
 
 
40
class StdinFromWeb(object):
 
41
    def __init__(self, cmdQ, lineQ):
 
42
        self.cmdQ = cmdQ
 
43
        self.lineQ = lineQ
 
44
 
 
45
    def readline(self):
 
46
        self.cmdQ.put({"input":None})
 
47
        expiry.ping()
 
48
        ln = self.lineQ.get()
 
49
        if 'chat' in ln:
 
50
            return ln['chat']
 
51
        if 'interrupt' in ln:
 
52
            raise Interrupt()
 
53
        if 'terminate' in ln:
 
54
            sys.exit(0)
 
55
 
 
56
class StdoutToWeb(object):
 
57
    def __init__(self, cmdQ, lineQ):
 
58
        self.cmdQ = cmdQ
 
59
        self.lineQ = lineQ
 
60
        self.remainder = ''
 
61
 
 
62
    def _trim_incomplete_final(self, stuff):
 
63
        '''Trim an incomplete UTF-8 character from the end of a string.
 
64
           Returns (trimmed_string, count_of_trimmed_bytes).
 
65
        '''
 
66
        tokill = incomplete_utf8_sequence(stuff)
 
67
        if tokill == 0:
 
68
            return (stuff, tokill)
 
69
        else:
 
70
            return (stuff[:-tokill], tokill)
 
71
 
 
72
    def write(self, stuff):
 
73
        # print will only give a non-file a unicode or str. There's no way
 
74
        # to convince it to encode unicodes, so we have to do it ourselves.
 
75
        # Yay for file special-cases (fileobject.c, PyFile_WriteObject).
 
76
        # If somebody wants to write some other object to here, they do it
 
77
        # at their own peril.
 
78
        if isinstance(stuff, unicode):
 
79
            stuff = stuff.encode('utf-8')
 
80
        self.remainder = self.remainder + stuff
 
81
 
 
82
        # if there's less than 128 bytes, buffer
 
83
        if len(self.remainder) < 128:
 
84
            return
 
85
 
 
86
        # if there's lots, then send it in 1/2K blocks
 
87
        while len(self.remainder) > 512:
 
88
            # We send things as Unicode inside JSON, so we must only send
 
89
            # complete UTF-8 characters.
 
90
            (blk, count) = self._trim_incomplete_final(self.remainder[:512])
 
91
            self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
 
92
            expiry.ping()
 
93
            ln = self.lineQ.get()
 
94
            self.remainder = self.remainder[512 - count:]
 
95
 
 
96
        # Finally, split the remainder up into lines, and ship all the
 
97
        # completed lines off to the server.
 
98
        lines = self.remainder.split("\n")
 
99
        self.remainder = lines[-1]
 
100
        del lines[-1]
 
101
 
 
102
        if len(lines) > 0:
 
103
            lines.append('')
 
104
            text = "\n".join(lines)
 
105
            self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
 
106
            expiry.ping()
 
107
            ln = self.lineQ.get()
 
108
            if 'interrupt' in ln:
 
109
                raise Interrupt()
 
110
 
 
111
    def flush(self):
 
112
        if len(self.remainder) > 0:
 
113
            (out, count) = self._trim_incomplete_final(self.remainder)
 
114
            self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
 
115
            expiry.ping()
 
116
            ln = self.lineQ.get()
 
117
            # Leave incomplete characters in the buffer.
 
118
            # Yes, this does mean that an incomplete character will be left
 
119
            # off the end, but we discussed this and it was deemed best.
 
120
            self.remainder = self.remainder[len(self.remainder)-count:]
 
121
            if 'interrupt' in ln:
 
122
                raise Interrupt()
 
123
 
 
124
class WebIO(object):
 
125
    """Provides a file like interface to the Web front end of the console.
 
126
    You may print text to the console using write(), flush any buffered output 
 
127
    using flush(), or request text from the console using readline()"""
 
128
    
 
129
    def __init__(self, cmdQ, lineQ):
 
130
        self.cmdQ = cmdQ
 
131
        self.lineQ = lineQ
 
132
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
 
133
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
 
134
 
 
135
    def write(self, stuff):
 
136
        self.stdout.write(stuff)
 
137
 
 
138
    def flush(self):
 
139
        self.stdout.flush()
 
140
 
 
141
    def readline(self):
 
142
        self.stdout.flush()
 
143
        return self.stdin.readline()
 
144
 
 
145
class PythonRunner(Thread):
 
146
    def __init__(self, cmdQ, lineQ):
 
147
        self.cmdQ = cmdQ
 
148
        self.lineQ = lineQ
 
149
        self.webio = WebIO(self.cmdQ, self.lineQ)
 
150
        Thread.__init__(self)
 
151
 
 
152
    def execCmd(self, cmd):
 
153
        try:
 
154
            sys.stdin = self.webio
 
155
            sys.stdout = self.webio
 
156
            sys.stderr = self.webio
 
157
            # We don't expect a return value - 'single' symbol prints it.
 
158
            eval(cmd, self.globs)
 
159
            self.webio.flush()
 
160
            self.cmdQ.put({"okay": None})
 
161
            self.curr_cmd = ''
 
162
        except:
 
163
            tb = format_exc_start(start=1)
 
164
            self.webio.flush()
 
165
            self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
166
            self.curr_cmd = ''
 
167
 
 
168
    def run(self):
 
169
        self.globs = {}
 
170
        self.globs['__builtins__'] = globals()['__builtins__']
 
171
        self.curr_cmd = ''
 
172
 
 
173
        while True:
 
174
            ln = self.lineQ.get()
 
175
            if 'chat' in ln:
 
176
                if self.curr_cmd == '':
 
177
                    self.curr_cmd = ln['chat']
 
178
                else:
 
179
                    self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
 
180
                try:
 
181
                    cmd = codeop.compile_command(self.curr_cmd, '<web session>')
 
182
                    if cmd is None:
 
183
                        # The command was incomplete,
 
184
                        # so send back a None, so the
 
185
                        # client can print a '...'
 
186
                        self.cmdQ.put({"more":None})
 
187
                    else:
 
188
                        self.execCmd(cmd)
 
189
                except:
 
190
                    tb = format_exc_start(start=3)
 
191
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
192
                    self.webio.flush()
 
193
                    self.curr_cmd = ''
 
194
            if 'block' in ln:
 
195
                # throw away a partial command.
 
196
                try:
 
197
                    cmd = compile(ln['block'], "<web session>", 'exec');
 
198
                    self.execCmd(cmd)
 
199
                except:
 
200
                    tb = format_exc_start(start=1)
 
201
                    self.webio.flush()
 
202
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
203
                    self.curr_cmd = ''
 
204
 
 
205
def daemonize():
 
206
    if os.fork():   # launch child and...
 
207
        os._exit(0) # kill off parent
 
208
    os.setsid()
 
209
    if os.fork():   # launch child and...
 
210
        os._exit(0) # kill off parent again.
 
211
    os.umask(077)
 
212
 
 
213
# The global 'magic' is the secret that the client and server share
 
214
# which is used to create and md5 digest to authenticate requests.
 
215
# It is assigned a real value at startup.
 
216
magic = ''
 
217
 
 
218
cmdQ = Queue.Queue()
 
219
lineQ = Queue.Queue()
 
220
interpThread = PythonRunner(cmdQ, lineQ)
 
221
terminate = None
 
222
 
 
223
# Default expiry time of 15 minutes
 
224
expiry = ExpiryTimer(15 * 60)
 
225
 
 
226
def initializer():
 
227
    interpThread.setDaemon(True)
 
228
    interpThread.start()
 
229
    signal.signal(signal.SIGXCPU, sig_handler)
 
230
    expiry.ping()
 
231
 
 
232
def sig_handler(signum, frame):
 
233
    """Handles response from signals"""
 
234
    global terminate
 
235
    if signum == signal.SIGXCPU:
 
236
        terminate = "CPU Time Limit Exceeded"
 
237
 
 
238
def dispatch_msg(msg):
 
239
    expiry.ping()
 
240
    lineQ.put({msg['cmd']:msg['text']})
 
241
    if terminate:
 
242
        raise common.chat.Terminate({"restart":terminate})
 
243
    return cmdQ.get()
 
244
 
 
245
def format_exc_start(start=0):
 
246
    etype, value, tb = sys.exc_info()
 
247
    tbbits = traceback.extract_tb(tb)[start:]
 
248
    list = ['Traceback (most recent call last):\n']
 
249
    list = list + traceback.format_list(tbbits)
 
250
    list = list + traceback.format_exception_only(etype, value)
 
251
    return ''.join(list)
 
252
 
 
253
def incomplete_utf8_sequence(byteseq):
 
254
    """
 
255
    str -> int
 
256
    Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
 
257
    the end of the string which comprise an incomplete UTF-8 character
 
258
    sequence.
 
259
 
 
260
    If the string is empty or ends with a complete character OR INVALID
 
261
    sequence, returns 0.
 
262
    Otherwise, returns 1-3 indicating the number of bytes in the final
 
263
    incomplete (but valid) character sequence.
 
264
 
 
265
    Does not check any bytes before the final sequence for correctness.
 
266
 
 
267
    >>> incomplete_utf8_sequence("")
 
268
    0
 
269
    >>> incomplete_utf8_sequence("xy")
 
270
    0
 
271
    >>> incomplete_utf8_sequence("xy\xc3\xbc")
 
272
    0
 
273
    >>> incomplete_utf8_sequence("\xc3")
 
274
    1
 
275
    >>> incomplete_utf8_sequence("\xbc\xc3")
 
276
    1
 
277
    >>> incomplete_utf8_sequence("xy\xbc\xc3")
 
278
    1
 
279
    >>> incomplete_utf8_sequence("xy\xe0\xa0")
 
280
    2
 
281
    >>> incomplete_utf8_sequence("xy\xf4")
 
282
    1
 
283
    >>> incomplete_utf8_sequence("xy\xf4\x8f")
 
284
    2
 
285
    >>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
 
286
    3
 
287
    """
 
288
    count = 0
 
289
    expect = None
 
290
    for b in byteseq[::-1]:
 
291
        b = ord(b)
 
292
        count += 1
 
293
        if b & 0x80 == 0x0:
 
294
            # 0xxxxxxx (single-byte character)
 
295
            expect = 1
 
296
            break
 
297
        elif b & 0xc0 == 0x80:
 
298
            # 10xxxxxx (subsequent byte)
 
299
            pass
 
300
        elif b & 0xe0 == 0xc0:
 
301
            # 110xxxxx (start of 2-byte sequence)
 
302
            expect = 2
 
303
            break
 
304
        elif b & 0xf0 == 0xe0:
 
305
            # 1110xxxx (start of 3-byte sequence)
 
306
            expect = 3
 
307
            break
 
308
        elif b & 0xf8 == 0xf0:
 
309
            # 11110xxx (start of 4-byte sequence)
 
310
            expect = 4
 
311
            break
 
312
        else:
 
313
            # Invalid byte
 
314
            return 0
 
315
 
 
316
        if count >= 4:
 
317
            # Seen too many "subsequent bytes", invalid
 
318
            return 0
 
319
 
 
320
    if expect is None:
 
321
        # We never saw a "first byte", invalid
 
322
        return 0
 
323
 
 
324
    # We now know expect and count
 
325
    if count >= expect:
 
326
        # Complete, or we saw an invalid sequence
 
327
        return 0
 
328
    elif count < expect:
 
329
        # Incomplete
 
330
        return count
 
331
 
 
332
if __name__ == "__main__":
 
333
    port = int(sys.argv[1])
 
334
    magic = sys.argv[2]
 
335
    
 
336
    # Sanitise the Enviroment
 
337
    os.environ = {}
 
338
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
 
339
 
 
340
    if len(sys.argv) >= 4:
 
341
        # working_dir
 
342
        os.chdir(sys.argv[3])
 
343
        os.environ['HOME'] = sys.argv[3]
 
344
 
 
345
    # Make python's search path follow the cwd
 
346
    sys.path[0] = ''
 
347
 
 
348
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)