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

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: mattgiuca
  • Date: 2008-01-11 00:49:06 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:172
util: Added buildurl function.

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 cPickle
9
 
import cStringIO
10
 
import md5
11
 
import os
12
 
import Queue
13
 
import signal
14
 
import socket
15
 
import sys
16
 
import traceback
17
 
from threading import Thread
18
 
 
19
 
import common.chat
20
 
import common.util
21
 
 
22
 
# This version must be supported by both the local and remote code
23
 
PICKLEVERSION = 0
24
 
 
25
 
class Interrupt(Exception):
26
 
    def __init__(self):
27
 
        Exception.__init__(self, "Interrupted!")
28
 
 
29
 
class ExpiryTimer(object):
30
 
    def __init__(self, idle):
31
 
        self.idle = idle
32
 
        signal.signal(signal.SIGALRM, self.timeout)
33
 
 
34
 
    def ping(self):
35
 
        signal.alarm(self.idle)
36
 
 
37
 
    def start(self, time):
38
 
        signal.alarm(time)
39
 
 
40
 
    def stop(self):
41
 
        self.ping()
42
 
 
43
 
    def timeout(self, signum, frame):
44
 
        sys.exit(1)
45
 
 
46
 
class StdinFromWeb(object):
47
 
    def __init__(self, cmdQ, lineQ):
48
 
        self.cmdQ = cmdQ
49
 
        self.lineQ = lineQ
50
 
 
51
 
    def readline(self):
52
 
        self.cmdQ.put({"input":None})
53
 
        expiry.ping()
54
 
        ln = self.lineQ.get()
55
 
        if 'chat' in ln:
56
 
            return ln['chat']
57
 
        if 'interrupt' in ln:
58
 
            raise Interrupt()
59
 
 
60
 
class StdoutToWeb(object):
61
 
    def __init__(self, cmdQ, lineQ):
62
 
        self.cmdQ = cmdQ
63
 
        self.lineQ = lineQ
64
 
        self.remainder = ''
65
 
 
66
 
    def _trim_incomplete_final(self, stuff):
67
 
        '''Trim an incomplete UTF-8 character from the end of a string.
68
 
           Returns (trimmed_string, count_of_trimmed_bytes).
69
 
        '''
70
 
        tokill = incomplete_utf8_sequence(stuff)
71
 
        if tokill == 0:
72
 
            return (stuff, tokill)
73
 
        else:
74
 
            return (stuff[:-tokill], tokill)
75
 
 
76
 
    def write(self, stuff):
77
 
        # print will only give a non-file a unicode or str. There's no way
78
 
        # to convince it to encode unicodes, so we have to do it ourselves.
79
 
        # Yay for file special-cases (fileobject.c, PyFile_WriteObject).
80
 
        # If somebody wants to write some other object to here, they do it
81
 
        # at their own peril.
82
 
        if isinstance(stuff, unicode):
83
 
            stuff = stuff.encode('utf-8')
84
 
        self.remainder = self.remainder + stuff
85
 
 
86
 
        # if there's less than 128 bytes, buffer
87
 
        if len(self.remainder) < 128:
88
 
            return
89
 
 
90
 
        # if there's lots, then send it in 1/2K blocks
91
 
        while len(self.remainder) > 512:
92
 
            # We send things as Unicode inside JSON, so we must only send
93
 
            # complete UTF-8 characters.
94
 
            (blk, count) = self._trim_incomplete_final(self.remainder[:512])
95
 
            self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
96
 
            expiry.ping()
97
 
            ln = self.lineQ.get()
98
 
            self.remainder = self.remainder[512 - count:]
99
 
 
100
 
        # Finally, split the remainder up into lines, and ship all the
101
 
        # completed lines off to the server.
102
 
        lines = self.remainder.split("\n")
103
 
        self.remainder = lines[-1]
104
 
        del lines[-1]
105
 
 
106
 
        if len(lines) > 0:
107
 
            lines.append('')
108
 
            text = "\n".join(lines)
109
 
            self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
110
 
            expiry.ping()
111
 
            ln = self.lineQ.get()
112
 
            if 'interrupt' in ln:
113
 
                raise Interrupt()
114
 
 
115
 
    def flush(self):
116
 
        if len(self.remainder) > 0:
117
 
            (out, count) = self._trim_incomplete_final(self.remainder)
118
 
            self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
119
 
            expiry.ping()
120
 
            ln = self.lineQ.get()
121
 
            # Leave incomplete characters in the buffer.
122
 
            # Yes, this does mean that an incomplete character will be left
123
 
            # off the end, but we discussed this and it was deemed best.
124
 
            self.remainder = self.remainder[len(self.remainder)-count:]
125
 
            if 'interrupt' in ln:
126
 
                raise Interrupt()
127
 
 
128
 
class WebIO(object):
129
 
    """Provides a file like interface to the Web front end of the console.
130
 
    You may print text to the console using write(), flush any buffered output 
131
 
    using flush(), or request text from the console using readline()"""
132
 
    
133
 
    def __init__(self, cmdQ, lineQ):
134
 
        self.cmdQ = cmdQ
135
 
        self.lineQ = lineQ
136
 
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
137
 
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
138
 
 
139
 
    def write(self, stuff):
140
 
        self.stdout.write(stuff)
141
 
 
142
 
    def flush(self):
143
 
        self.stdout.flush()
144
 
 
145
 
    def readline(self):
146
 
        self.stdout.flush()
147
 
        return self.stdin.readline()
148
 
 
149
 
class PythonRunner(Thread):
150
 
    def __init__(self, cmdQ, lineQ):
151
 
        self.cmdQ = cmdQ
152
 
        self.lineQ = lineQ
153
 
        self.webio = WebIO(self.cmdQ, self.lineQ)
154
 
        Thread.__init__(self)
155
 
 
156
 
    def execCmd(self, cmd):
157
 
        try:
158
 
            sys.stdin = self.webio
159
 
            sys.stdout = self.webio
160
 
            sys.stderr = self.webio
161
 
            # We don't expect a return value - 'single' symbol prints it.
162
 
            self.eval(cmd)
163
 
            self.webio.flush()
164
 
            self.cmdQ.put({"okay": None})
165
 
            self.curr_cmd = ''
166
 
        except:
167
 
            tb = format_exc_start(start=1)
168
 
            self.webio.flush()
169
 
            self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
170
 
            self.curr_cmd = ''
171
 
 
172
 
    def run(self):
173
 
        self.globs = {}
174
 
        self.globs['__builtins__'] = globals()['__builtins__']
175
 
        self.curr_cmd = ''
176
 
 
177
 
        while True:
178
 
            ln = self.lineQ.get()
179
 
            if 'chat' in ln:
180
 
                if self.curr_cmd == '':
181
 
                    self.curr_cmd = ln['chat']
182
 
                else:
183
 
                    self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
184
 
                try:
185
 
                    cmd = codeop.compile_command(self.curr_cmd, '<web session>')
186
 
                    if cmd is None:
187
 
                        # The command was incomplete,
188
 
                        # so send back a None, so the
189
 
                        # client can print a '...'
190
 
                        self.cmdQ.put({"more":None})
191
 
                    else:
192
 
                        self.execCmd(cmd)
193
 
                except:
194
 
                    tb = format_exc_start(start=3)
195
 
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
196
 
                    self.webio.flush()
197
 
                    self.curr_cmd = ''
198
 
            elif 'block' in ln:
199
 
                # throw away a partial command.
200
 
                try:
201
 
                    cmd = compile(ln['block'], "<web session>", 'exec');
202
 
                    self.execCmd(cmd)
203
 
                except:
204
 
                    tb = format_exc_start(start=1)
205
 
                    self.webio.flush()
206
 
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
207
 
                    self.curr_cmd = ''
208
 
            elif 'flush' in ln:
209
 
                # reset the globals
210
 
                self.globs = {}
211
 
                self.globs['__builtins__'] = globals()['__builtins__']
212
 
                self.cmdQ.put({'response': 'okay'})
213
 
                # Unpickle the new space (if provided)
214
 
                if isinstance(ln['flush'],dict):
215
 
                    for g in ln['flush']:
216
 
                        try:
217
 
                            self.globs[g] = cPickle.loads(ln['flush'][g])
218
 
                        except:
219
 
                            pass
220
 
            elif 'call' in ln:
221
 
                call = {}
222
 
                stdout = cStringIO.StringIO()
223
 
                stderr = cStringIO.StringIO()
224
 
                sys.stdout = stdout
225
 
                sys.stderr = stderr
226
 
 
227
 
                if isinstance(ln['call'], dict):
228
 
                    params = ln['call']
229
 
                    try:
230
 
                        # Expand parameters
231
 
                        if isinstance(params['args'], list):
232
 
                            args = map(self.eval, params['args'])
233
 
                        else:
234
 
                            args = []
235
 
                        if isinstance(params['kwargs'], dict):
236
 
                            kwargs = {}
237
 
                            for kwarg in params['kwargs']:
238
 
                                kwargs[kwarg] = self.eval(
239
 
                                    params['kwargs'][kwarg])
240
 
                        else:
241
 
                            kwargs = {}
242
 
 
243
 
                        # Run the fuction
244
 
                        function = self.eval(params['function'])
245
 
                        try:
246
 
                            call['result'] = function(*args, **kwargs)
247
 
                        except Exception, e:
248
 
                            exception = {}
249
 
                            tb = format_exc_start(start=1)
250
 
                            exception['traceback'] = \
251
 
                                ''.join(tb).decode('utf-8', 'replace')
252
 
                            exception['except'] = cPickle.dumps(e,
253
 
                                PICKLEVERSION)
254
 
                            call['exception'] = exception
255
 
                    except Exception, e:
256
 
                        tb = format_exc_start(start=1)
257
 
                        self.cmdQ.put(
258
 
                            {"exc": ''.join(tb).decode('utf-8', 'replace')})
259
 
                    
260
 
                    # Write out the inspection object
261
 
                    call['stdout'] = stdout.getvalue()
262
 
                    call['stderr'] = stderr.getvalue()
263
 
                    self.cmdQ.put(call)
264
 
                else:
265
 
                    self.cmdQ.put({'response': 'failure'})
266
 
                stdout.close()
267
 
                stderr.close()
268
 
                self.curr_cmd = ''
269
 
            elif 'inspect' in ln:
270
 
                # Like block but return a serialization of the state
271
 
                # throw away partial command
272
 
                inspection = {}
273
 
                stdout = cStringIO.StringIO()
274
 
                stderr = cStringIO.StringIO()
275
 
                try:
276
 
                    cmd = compile(ln['inspect'], "<web session>", 'exec');
277
 
                    sys.stdin = None
278
 
                    sys.stdout = stdout
279
 
                    sys.stderr = stderr
280
 
                    # We don't expect a return value - 'single' symbol prints 
281
 
                    # it.
282
 
                    self.eval(cmd)
283
 
                except Exception, e:
284
 
                    exception = {}
285
 
                    tb = format_exc_start(start=1)
286
 
                    exception['traceback'] = \
287
 
                        ''.join(tb).decode('utf-8', 'replace')
288
 
                    exception['except'] = cPickle.dumps(e, PICKLEVERSION)
289
 
                    inspection['exception'] = exception                
290
 
                
291
 
                # Write out the inspection object
292
 
                inspection['stdout'] = stdout.getvalue()
293
 
                inspection['stderr'] = stderr.getvalue()
294
 
                inspection['globals'] = flatten(self.globs)
295
 
                self.cmdQ.put(inspection)
296
 
                stdout.close()
297
 
                stderr.close()
298
 
                self.curr_cmd = ''
299
 
            elif 'set_vars' in ln:
300
 
                # Adds some variables to the global dictionary
301
 
                for var in ln['set_vars']:
302
 
                    try:
303
 
                        self.globs[var] = self.eval(ln['set_vars'][var])
304
 
                    except Exception, e:
305
 
                        tb = format_exc_start(start=1)
306
 
                        self.cmdQ.put(
307
 
                            {"exc": ''.join(tb).decode('utf-8', 'replace')})
308
 
 
309
 
                self.cmdQ.put({'response': 'okay'})
310
 
            else:
311
 
                raise Exception, "Invalid Command"
312
 
 
313
 
    def eval(self, source):
314
 
        """ Evaluates a string in the private global space """
315
 
        return eval(source, self.globs)
316
 
 
317
 
def daemonize():
318
 
    if os.fork():   # launch child and...
319
 
        os._exit(0) # kill off parent
320
 
    os.setsid()
321
 
    if os.fork():   # launch child and...
322
 
        os._exit(0) # kill off parent again.
323
 
    os.umask(077)
324
 
 
325
 
# The global 'magic' is the secret that the client and server share
326
 
# which is used to create and md5 digest to authenticate requests.
327
 
# It is assigned a real value at startup.
328
 
magic = ''
329
 
 
330
 
cmdQ = Queue.Queue()
331
 
lineQ = Queue.Queue()
332
 
interpThread = PythonRunner(cmdQ, lineQ)
333
 
terminate = None
334
 
 
335
 
# Default expiry time of 15 minutes
336
 
expiry = ExpiryTimer(15 * 60)
337
 
 
338
 
def initializer():
339
 
    interpThread.setDaemon(True)
340
 
    interpThread.start()
341
 
    signal.signal(signal.SIGXCPU, sig_handler)
342
 
    expiry.ping()
343
 
 
344
 
def sig_handler(signum, frame):
345
 
    """Handles response from signals"""
346
 
    global terminate
347
 
    if signum == signal.SIGXCPU:
348
 
        terminate = "CPU Time Limit Exceeded"
349
 
 
350
 
def dispatch_msg(msg):
351
 
    global terminate
352
 
    if msg['cmd'] == 'restart':
353
 
        terminate = "User requested console be reset"
354
 
    if terminate:
355
 
        raise common.chat.Terminate({"restart":terminate})
356
 
    expiry.ping()
357
 
    lineQ.put({msg['cmd']:msg['text']})
358
 
    if terminate:
359
 
        raise common.chat.Terminate({"restart":terminate})
360
 
    return cmdQ.get()
361
 
 
362
 
def format_exc_start(start=0):
363
 
    etype, value, tb = sys.exc_info()
364
 
    tbbits = traceback.extract_tb(tb)[start:]
365
 
    list = ['Traceback (most recent call last):\n']
366
 
    list = list + traceback.format_list(tbbits)
367
 
    list = list + traceback.format_exception_only(etype, value)
368
 
    return ''.join(list)
369
 
 
370
 
def incomplete_utf8_sequence(byteseq):
371
 
    """
372
 
    str -> int
373
 
    Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
374
 
    the end of the string which comprise an incomplete UTF-8 character
375
 
    sequence.
376
 
 
377
 
    If the string is empty or ends with a complete character OR INVALID
378
 
    sequence, returns 0.
379
 
    Otherwise, returns 1-3 indicating the number of bytes in the final
380
 
    incomplete (but valid) character sequence.
381
 
 
382
 
    Does not check any bytes before the final sequence for correctness.
383
 
 
384
 
    >>> incomplete_utf8_sequence("")
385
 
    0
386
 
    >>> incomplete_utf8_sequence("xy")
387
 
    0
388
 
    >>> incomplete_utf8_sequence("xy\xc3\xbc")
389
 
    0
390
 
    >>> incomplete_utf8_sequence("\xc3")
391
 
    1
392
 
    >>> incomplete_utf8_sequence("\xbc\xc3")
393
 
    1
394
 
    >>> incomplete_utf8_sequence("xy\xbc\xc3")
395
 
    1
396
 
    >>> incomplete_utf8_sequence("xy\xe0\xa0")
397
 
    2
398
 
    >>> incomplete_utf8_sequence("xy\xf4")
399
 
    1
400
 
    >>> incomplete_utf8_sequence("xy\xf4\x8f")
401
 
    2
402
 
    >>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
403
 
    3
404
 
    """
405
 
    count = 0
406
 
    expect = None
407
 
    for b in byteseq[::-1]:
408
 
        b = ord(b)
409
 
        count += 1
410
 
        if b & 0x80 == 0x0:
411
 
            # 0xxxxxxx (single-byte character)
412
 
            expect = 1
413
 
            break
414
 
        elif b & 0xc0 == 0x80:
415
 
            # 10xxxxxx (subsequent byte)
416
 
            pass
417
 
        elif b & 0xe0 == 0xc0:
418
 
            # 110xxxxx (start of 2-byte sequence)
419
 
            expect = 2
420
 
            break
421
 
        elif b & 0xf0 == 0xe0:
422
 
            # 1110xxxx (start of 3-byte sequence)
423
 
            expect = 3
424
 
            break
425
 
        elif b & 0xf8 == 0xf0:
426
 
            # 11110xxx (start of 4-byte sequence)
427
 
            expect = 4
428
 
            break
429
 
        else:
430
 
            # Invalid byte
431
 
            return 0
432
 
 
433
 
        if count >= 4:
434
 
            # Seen too many "subsequent bytes", invalid
435
 
            return 0
436
 
 
437
 
    if expect is None:
438
 
        # We never saw a "first byte", invalid
439
 
        return 0
440
 
 
441
 
    # We now know expect and count
442
 
    if count >= expect:
443
 
        # Complete, or we saw an invalid sequence
444
 
        return 0
445
 
    elif count < expect:
446
 
        # Incomplete
447
 
        return count
448
 
 
449
 
# Takes an object and returns a flattened version suitable for JSON
450
 
def flatten(object):
451
 
    flat = {}
452
 
    for o in object:
453
 
        try:
454
 
            flat[o] = cPickle.dumps(object[o], PICKLEVERSION)
455
 
        except TypeError:
456
 
            try:
457
 
                o_type = type(object[o]).__name__
458
 
                o_name = object[o].__name__
459
 
                fake_o = common.util.FakeObject(o_type, o_name)
460
 
                flat[o] = cPickle.dumps(fake_o, PICKLEVERSION)
461
 
            except AttributeError:
462
 
                pass
463
 
    return flat
464
 
 
465
 
if __name__ == "__main__":
466
 
    port = int(sys.argv[1])
467
 
    magic = sys.argv[2]
468
 
    
469
 
    # Sanitise the Enviroment
470
 
    os.environ = {}
471
 
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
472
 
 
473
 
    if len(sys.argv) >= 4:
474
 
        # working_dir
475
 
        os.chdir(sys.argv[3])
476
 
        os.environ['HOME'] = sys.argv[3]
477
 
 
478
 
    # Make python's search path follow the cwd
479
 
    sys.path[0] = ''
480
 
 
481
 
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)