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

« back to all changes in this revision

Viewing changes to services/python-console

  • Committer: drtomc
  • Date: 2007-12-11 03:26:29 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:25
A bit more work on the userdb stuff.

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