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

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: wagrant
  • Date: 2008-09-18 09:23:56 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1050
setup: Don't give possible options in the top-level usage string.
setup.build: Bail out if there is no jail and we've not been told to
             build one.
setup.setuputil: Don't import pysvn except where it is needed, so we
                 can at least display the usage without extra deps.

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
        action, params = self.lineQ.get()
 
55
        if action == 'chat':
 
56
            return params
 
57
        elif action == 'interrupt':
 
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 = common.util.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
            action, params = 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
            action, params = self.lineQ.get()
 
112
            if action == 'interrupt':
 
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
            action, params = 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 action == 'interrupt':
 
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
    # FIXME: Clean up the whole stdin, stdout, stderr mess. We really need to 
 
133
    # be able to deal with the streams individually.
 
134
    
 
135
    def __init__(self, cmdQ, lineQ):
 
136
        self.cmdQ = cmdQ
 
137
        self.lineQ = lineQ
 
138
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
 
139
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
 
140
 
 
141
    def write(self, stuff):
 
142
        self.stdout.write(stuff)
 
143
 
 
144
    def flush(self):
 
145
        self.stdout.flush()
 
146
 
 
147
    def readline(self):
 
148
        self.stdout.flush()
 
149
        return self.stdin.readline()
 
150
 
 
151
class PythonRunner(Thread):
 
152
    def __init__(self, cmdQ, lineQ):
 
153
        self.cmdQ = cmdQ
 
154
        self.lineQ = lineQ
 
155
        self.webio = WebIO(self.cmdQ, self.lineQ)
 
156
        Thread.__init__(self)
 
157
 
 
158
    def execCmd(self, cmd):
 
159
        try:
 
160
            # We don't expect a return value - 'single' symbol prints it.
 
161
            self.eval(cmd)
 
162
            self.curr_cmd = ''
 
163
            self.webio.flush()
 
164
            return({"okay": None})
 
165
        except:
 
166
            self.curr_cmd = ''
 
167
            self.webio.flush()
 
168
            tb = format_exc_start(start=2)
 
169
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
170
 
 
171
    def run(self):
 
172
        # Set up global space and partial command buffer
 
173
        self.globs = {}
 
174
        self.curr_cmd = ''
 
175
 
 
176
        # Set up I/O to use web interface
 
177
        sys.stdin = self.webio
 
178
        sys.stdout = self.webio
 
179
        sys.stderr = self.webio
 
180
 
 
181
        # Handlers for each action
 
182
        actions = {
 
183
            'chat': self.handle_chat,
 
184
            'block': self.handle_block,
 
185
            'globals': self.handle_globals,
 
186
            'call': self.handle_call,
 
187
            'execute': self.handle_execute,
 
188
            'setvars': self.handle_setvars,
 
189
            }
 
190
 
 
191
        # Run the processing loop
 
192
        while True:
 
193
            action, params = self.lineQ.get()
 
194
            try:
 
195
                response = actions[action](params)
 
196
            except Exception, e:
 
197
                response = {'error': repr(e)}
 
198
            finally:
 
199
                self.cmdQ.put(response)
 
200
                   
 
201
    def handle_chat(self, params):
 
202
        # Set up the partial cmd buffer
 
203
        if self.curr_cmd == '':
 
204
            self.curr_cmd = params
 
205
        else:
 
206
            self.curr_cmd = self.curr_cmd + '\n' + params
 
207
 
 
208
        # Try to execute the buffer
 
209
        try:
 
210
            cmd = codeop.compile_command(self.curr_cmd, '<web session>')
 
211
            if cmd is None:
 
212
                # The command was incomplete, so send back a None, so the              
 
213
                # client can print a '...'
 
214
                return({"more":None})
 
215
            else:
 
216
                return(self.execCmd(cmd))
 
217
        except:
 
218
            # Clear any partial command
 
219
            self.curr_cmd = ''
 
220
            # Flush the output buffers
 
221
            sys.stderr.flush()
 
222
            sys.stdout.flush()
 
223
            # Return the exception
 
224
            tb = format_exc_start(start=3)
 
225
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
226
 
 
227
    def handle_block(self, params):
 
228
        # throw away any partial command.
 
229
        self.curr_cmd = ''
 
230
 
 
231
        # Try to execute a complete block of code
 
232
        try:
 
233
            cmd = compile(params, "<web session>", 'exec');
 
234
            return(self.execCmd(cmd))
 
235
        except:
 
236
            # Flush the output buffers
 
237
            sys.stderr.flush()
 
238
            sys.stdout.flush()
 
239
            # Return the exception
 
240
            tb = format_exc_start(start=1)
 
241
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
242
 
 
243
    def handle_globals(self, params):
 
244
        # Unpickle the new space (if provided)
 
245
        if isinstance(params, dict):
 
246
            self.globs = {}
 
247
            for g in params:
 
248
                try:
 
249
                    self.globs[g] = cPickle.loads(params[g])
 
250
                except:
 
251
                    pass
 
252
 
 
253
        # Return the current globals
 
254
        return({'globals': flatten(self.globs)})
 
255
 
 
256
    def handle_call(self, params):
 
257
        call = {}
 
258
        
 
259
        # throw away any partial command.
 
260
        self.curr_cmd = ''
 
261
 
 
262
        if isinstance(params, dict):
 
263
            try:
 
264
                # Expand parameters
 
265
                if isinstance(params['args'], list):
 
266
                    args = map(self.eval, params['args'])
 
267
                else:
 
268
                    args = []
 
269
                if isinstance(params['kwargs'], dict):
 
270
                    kwargs = {}
 
271
                    for kwarg in params['kwargs']:
 
272
                        kwargs[kwarg] = self.eval(
 
273
                            params['kwargs'][kwarg])
 
274
                else:
 
275
                    kwargs = {}
 
276
 
 
277
                # Run the fuction
 
278
                function = self.eval(params['function'])
 
279
                try:
 
280
                    call['result'] = function(*args, **kwargs)
 
281
                except Exception, e:
 
282
                    exception = {}
 
283
                    tb = format_exc_start(start=1)
 
284
                    exception['traceback'] = \
 
285
                        ''.join(tb).decode('utf-8', 'replace')
 
286
                    exception['except'] = cPickle.dumps(e,
 
287
                        PICKLEVERSION)
 
288
                    call['exception'] = exception
 
289
            except Exception, e:
 
290
                tb = format_exc_start(start=1)
 
291
                call = {"exc": ''.join(tb).decode('utf-8', 'replace')}
 
292
            
 
293
            # Flush the output buffers
 
294
            sys.stderr.flush()
 
295
            sys.stdout.flush()
 
296
 
 
297
            # Write out the inspection object
 
298
            return(call)
 
299
        else:
 
300
            return({'response': 'failure'})
 
301
 
 
302
    def handle_execute(self, params):
 
303
        # throw away any partial command.
 
304
        self.curr_cmd = ''
 
305
        
 
306
        # Like block but return a serialization of the state
 
307
        # throw away partial command
 
308
        response = {'okay': None}
 
309
        try:
 
310
            cmd = compile(params, "<web session>", 'exec');
 
311
            # We don't expect a return value - 'single' symbol prints it.
 
312
            self.eval(cmd)
 
313
        except Exception, e:
 
314
            response = {'exception': cPickle.dumps(e, PICKLEVERSION)}
 
315
           
 
316
        # Flush the output
 
317
        sys.stderr.flush()
 
318
        sys.stdout.flush()
 
319
               
 
320
        # Return the inspection object
 
321
        return(response)
 
322
 
 
323
    def handle_setvars(self, params):
 
324
        # Adds some variables to the global dictionary
 
325
        for var in params['set_vars']:
 
326
            try:
 
327
                self.globs[var] = self.eval(params['set_vars'][var])
 
328
            except Exception, e:
 
329
                tb = format_exc_start(start=1)
 
330
                return({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
331
 
 
332
        return({'okay': None})
 
333
 
 
334
    def eval(self, source):
 
335
        """ Evaluates a string in the private global space """
 
336
        return eval(source, self.globs)
 
337
 
 
338
# The global 'magic' is the secret that the client and server share
 
339
# which is used to create and md5 digest to authenticate requests.
 
340
# It is assigned a real value at startup.
 
341
magic = ''
 
342
 
 
343
cmdQ = Queue.Queue()
 
344
lineQ = Queue.Queue()
 
345
interpThread = PythonRunner(cmdQ, lineQ)
 
346
terminate = None
 
347
 
 
348
# Default expiry time of 15 minutes
 
349
expiry = ExpiryTimer(15 * 60)
 
350
 
 
351
def initializer():
 
352
    interpThread.setDaemon(True)
 
353
    interpThread.start()
 
354
    signal.signal(signal.SIGXCPU, sig_handler)
 
355
    expiry.ping()
 
356
 
 
357
def sig_handler(signum, frame):
 
358
    """Handles response from signals"""
 
359
    global terminate
 
360
    if signum == signal.SIGXCPU:
 
361
        terminate = "CPU Time Limit Exceeded"
 
362
 
 
363
def dispatch_msg(msg):
 
364
    global terminate
 
365
    if msg['cmd'] == 'terminate':
 
366
        terminate = "User requested console be terminated"
 
367
    if terminate:
 
368
        raise common.chat.Terminate({"terminate":terminate})
 
369
    expiry.ping()
 
370
    lineQ.put((msg['cmd'],msg['text']))
 
371
    response = cmdQ.get()
 
372
    if terminate:
 
373
        raise common.chat.Terminate({"terminate":terminate})
 
374
    return response
 
375
 
 
376
def format_exc_start(start=0):
 
377
    etype, value, tb = sys.exc_info()
 
378
    tbbits = traceback.extract_tb(tb)[start:]
 
379
    list = ['Traceback (most recent call last):\n']
 
380
    list = list + traceback.format_list(tbbits)
 
381
    list = list + traceback.format_exception_only(etype, value)
 
382
    return ''.join(list)
 
383
 
 
384
 
 
385
# Takes an object and returns a flattened version suitable for JSON
 
386
def flatten(object):
 
387
    flat = {}
 
388
    for o in object:
 
389
        try:
 
390
            flat[o] = cPickle.dumps(object[o], PICKLEVERSION)
 
391
        except TypeError:
 
392
            try:
 
393
                o_type = type(object[o]).__name__
 
394
                o_name = object[o].__name__
 
395
                fake_o = common.util.FakeObject(o_type, o_name)
 
396
                flat[o] = cPickle.dumps(fake_o, PICKLEVERSION)
 
397
            except AttributeError:
 
398
                pass
 
399
    return flat
 
400
 
 
401
if __name__ == "__main__":
 
402
    port = int(sys.argv[1])
 
403
    magic = sys.argv[2]
 
404
    
 
405
    # Sanitise the Enviroment
 
406
    os.environ = {}
 
407
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
 
408
 
 
409
    if len(sys.argv) >= 4:
 
410
        # working_dir
 
411
        os.chdir(sys.argv[3])
 
412
        os.environ['HOME'] = sys.argv[3]
 
413
 
 
414
    # Make python's search path follow the cwd
 
415
    sys.path[0] = ''
 
416
 
 
417
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)