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

1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
1
# IVLE
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# Module: Console
19
# Author: Matt Giuca, Tom Conway, David Coles (refactor)
20
# Date: 13/8/2008
21
22
# Mainly refactored out of consoleservice
23
24
import errno
25
import cPickle
1226 by Matt Giuca
ivle.console: Replaced md5 module with hashlib. (md5 is deprecated and
26
import hashlib
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
27
import os
28
import random
29
import socket
30
import StringIO
31
import uuid
32
33
import cjson
34
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
35
from ivle import chat, interpret
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
36
37
class ConsoleError(Exception):
38
    """ The console failed in some way. This is bad. """
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
39
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
40
41
class ConsoleException(Exception):
42
    """ The code being exectuted on the console returned an exception. 
43
    """
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
44
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
45
46
class TruncateStringIO(StringIO.StringIO):
47
    """ A class that wraps around StringIO and truncates the buffer when the 
48
    contents are read (except for when using getvalue).
49
    """
50
    def __init__(self, buffer=None):
51
        StringIO.StringIO.__init__(self, buffer)
52
    
53
    def read(self, n=-1):
54
        """ Read at most size bytes from the file (less if the read hits EOF 
55
        before obtaining size bytes).
56
57
        If the size argument is negative or omitted, read all data until EOF is      
58
        reached. The bytes are returned as a string object. An empty string is 
59
        returned when EOF is encountered immediately.
60
61
        Truncates the buffer.
62
        """
63
64
        self.seek(0)
65
        res = StringIO.StringIO.read(self, n)
66
        self.truncate(0)
67
        return res
68
69
    def readline(self, length=None):
70
        """ Read one entire line from the file.
71
 
72
        A trailing newline character is kept in the string (but may be absent 
73
        when a file ends with an incomplete line). If the size argument is   
74
        present and non-negative, it is a maximum byte count (including the      
75
        trailing newline) and an incomplete line may be returned.
76
77
        An empty string is returned only when EOF is encountered immediately.
78
        
79
        Note: Unlike stdio's fgets(), the returned string contains null   
80
        characters ('\0') if they occurred in the input.
81
82
        Removes the line from the buffer.
83
        """
84
85
        self.seek(0)
86
        res = StringIO.StringIO.readline(self, length)
87
        rest = StringIO.StringIO.read(self)
88
        self.truncate(0)
89
        self.write(rest)
90
        return res
91
92
    def readlines(self, sizehint=0):
93
        """ Read until EOF using readline() and return a list containing the        
94
        lines thus read.
95
        
96
        If the optional sizehint argument is present, instead of reading up to 
97
        EOF, whole lines totalling approximately sizehint bytes (or more to      
98
        accommodate a final whole line).
99
100
        Truncates the buffer.
101
        """
102
103
        self.seek(0)
104
        res = StringIO.StringIO.readlines(self, length)
105
        self.truncate(0)
106
        return res
107
108
class Console(object):
109
    """ Provides a nice python interface to the console
110
    """
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
111
    def __init__(self, config, user, jail_path, working_dir):
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
112
        """Starts up a console service for user uid, inside chroot jail 
113
        jail_path with work directory of working_dir
114
        """
115
        super(Console, self).__init__()
116
1275 by William Grant
Kill ivle.console's dependency on ivle.conf.
117
        self.config = config
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
118
        self.user = user
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
119
        self.jail_path = jail_path
120
        self.working_dir = working_dir
121
122
        # Set up the buffers
123
        self.stdin = TruncateStringIO()
124
        self.stdout = TruncateStringIO()
125
        self.stderr = TruncateStringIO()
126
127
        # Fire up the console
128
        self.restart()
129
130
    def restart(self):
131
        # Empty all the buffers
132
        self.stdin.truncate(0)
133
        self.stdout.truncate(0)
134
        self.stderr.truncate(0)
135
136
        # TODO: Check if we are already running a console. If we are shut it 
137
        # down first.
138
139
        # TODO: Figure out the host name the console server is running on.
140
        self.host = socket.gethostname()
141
142
        # Create magic
143
        # TODO
1226 by Matt Giuca
ivle.console: Replaced md5 module with hashlib. (md5 is deprecated and
144
        self.magic = hashlib.md5(uuid.uuid4().bytes).hexdigest()
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
145
146
        # Try to find a free port on the server.
147
        # Just try some random ports in the range [3000,8000)
148
        # until we either succeed, or give up. If you think this
149
        # sounds risky, it isn't:
150
        # For N ports (e.g. 5000) with k (e.g. 100) in use, the
151
        # probability of failing to find a free port in t (e.g. 5) tries
152
        # is (k / N) ** t (e.g. 3.2*10e-9).
153
154
        tries = 0
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
155
        error = None
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
156
        while tries < 5:
157
            self.port = int(random.uniform(3000, 8000))
158
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
159
            python_console = os.path.join(self.config['paths']['share'],
160
                        'services/python-console')
161
            args = [python_console, str(self.port), str(self.magic)]
162
163
            try:
164
                interpret.execute_raw(self.config, self.user, self.jail_path,
165
                        self.working_dir, "/usr/bin/python", args)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
166
                # success
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
167
                break
168
            except interpret.ExecutionError, e:
169
                tries += 1
170
                error = e.message
171
172
        # If we can't start the console after 5 attemps (can't find a free 
173
        # port during random probing, syntax errors, segfaults) throw an 
174
        # exception.
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
175
        if tries == 5:
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
176
            raise ConsoleError('Unable to start console service: %s'%error)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
177
178
    def __chat(self, cmd, args):
179
        """ A wrapper around chat.chat to comunicate directly with the 
180
        console.
181
        """
182
        try:
183
            response = chat.chat(self.host, self.port,
184
                {'cmd': cmd, 'text': args}, self.magic)
185
        except socket.error, (enumber, estring):
186
            if enumber == errno.ECONNREFUSED:
187
                # Timeout
188
                raise ConsoleError(
189
                    "Could not establish a connection to the python console")
190
            else:
191
                # Some other error - probably serious
192
                raise socket.error, (enumber, estring)
193
        except cjson.DecodeError:
194
            # Couldn't decode the JSON
195
            raise ConsoleError(
196
                "Could not understand the python console response")
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
197
        except chat.ProtocolError, e:
198
            raise ConsoleError(*e.args)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
199
200
        return response
201
202
    def __handle_chat(self, cmd, args):
203
        """ A wrapper around self.__chat that handles all the messy responses 
204
        of chat for higher level interfaces such as inspect
205
        """
206
        # Do the request
207
        response = self.__chat(cmd, args)
208
209
        # Process I/O requests
210
        while 'output' in response or 'input' in response:
211
            if 'output' in response:
212
                self.stdout.write(response['output'])
213
                response = self.chat()
214
            elif 'input' in response:
215
                response = self.chat(self.stdin.readline())
216
217
        # Process user exceptions
218
        if 'exc' in response:
219
            raise ConsoleException(response['exc'])
220
221
        return response
222
223
    def chat(self, code=''):
224
        """ Executes a partial block of code """
225
        return self.__chat('chat', code)
226
227
    def block(self, code):
228
        """ Executes a block of code and returns the output """
229
        block = self.__handle_chat('block', code)
230
        if 'output' in block:
231
            return block['output']
232
        elif 'okay' in block:
233
            return
234
        else:
235
            raise ConsoleException("Bad response from console: %s"%str(block))
236
237
    def globals(self, globs=None):
238
        """ Returns a dictionary of the console's globals and optionally set 
239
        them to a new value
240
        """
241
        # Pickle the globals
242
        pickled_globs = None
243
        if globs is not None:
244
            pickled_globs = {}
245
            for g in globs:
246
                pickled_globs[g] = cPickle.dumps(globs[g])
247
248
        globals = self.__handle_chat('globals', pickled_globs)
249
250
        # Unpickle the globals
251
        for g in globals['globals']:
252
            globals['globals'][g] = cPickle.loads(globals['globals'][g])
253
254
        return globals['globals']
255
        
256
257
    def call(self, function, *args, **kwargs):
258
        """ Calls a function in the python console. Can take in a list of 
259
        repr() args and dictionary of repr() values kwargs. These will be 
260
        evaluated on the server side.
261
        """
262
        call_args = {
263
            'function': function,
264
            'args': args,
265
            'kwargs': kwargs}
266
        call = self.__handle_chat('call', call_args)
267
268
        # Unpickle any exceptions
269
        if 'exception' in call:
270
            call['exception']['except'] = \
271
                cPickle.loads(call['exception']['except'])
272
273
        return call
274
275
    def execute(self, code=''):
276
        """ Runs a block of code in the python console.
277
        If an exception was thrown then returns an exception object.
278
        """
279
        execute = self.__handle_chat('execute', code)
280
              
281
        # Unpickle any exceptions
282
        if 'exception' in execute:
1397.1.3 by William Grant
In the Console wrapper, unpickle the exception into the original dict and return that, not just the exception.
283
            execute['exception'] = cPickle.loads(execute['exception'])
284
        return execute
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
285
286
287
    def set_vars(self, variables):
288
        """ Takes a dictionary of varibles to add to the console's global 
289
        space. These are evaluated in the local space so you can't use this to 
290
        set a varible to a value to be calculated on the console side.
291
        """
292
        vars = {}
293
        for v in variables:
294
            vars[v] = repr(variables[v])
295
296
        set_vars = self.__handle_chat('set_vars', vars)
297
298
        if set_vars.get('response') != 'okay':
299
            raise ConsoleError("Could not set variables")
300
301
    def close(self):
302
        """ Causes the console process to terminate """
303
        return self.__chat('terminate', None)
304
    
305
class ExistingConsole(Console):
306
    """ Provides a nice python interface to an existing console.
307
    Note: You can't restart an existing console since there is no way to infer 
308
    all the starting parameters. Just start a new Console instead.
309
    """
310
    def __init__(self, host, port, magic):
311
        self.host = host
312
        self.port = port
313
        self.magic = magic
314
315
        # Set up the buffers
316
        self.stdin = TruncateStringIO()
317
        self.stdout = TruncateStringIO()
318
        self.stderr = TruncateStringIO()
319
320
    def restart():
321
        raise NotImplementedError('You can not restart an existing console')
322