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

« back to all changes in this revision

Viewing changes to ivle/console.py

  • 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
 
# 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
26
 
import hashlib
27
 
import os
28
 
import random
29
 
import socket
30
 
import StringIO
31
 
import uuid
32
 
 
33
 
import cjson
34
 
 
35
 
from ivle import chat, interpret
36
 
 
37
 
class ConsoleError(Exception):
38
 
    """ The console failed in some way. This is bad. """
39
 
    pass
40
 
 
41
 
class ConsoleException(Exception):
42
 
    """ The code being exectuted on the console returned an exception. 
43
 
    """
44
 
    pass
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
 
    """
111
 
    def __init__(self, config, user, jail_path, working_dir):
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
 
 
117
 
        self.config = config
118
 
        self.user = user
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
144
 
        self.magic = hashlib.md5(uuid.uuid4().bytes).hexdigest()
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
155
 
        error = None
156
 
        while tries < 5:
157
 
            self.port = int(random.uniform(3000, 8000))
158
 
 
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)
166
 
                # success
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.
175
 
        if tries == 5:
176
 
            raise ConsoleError('Unable to start console service: %s'%error)
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")
197
 
        except chat.ProtocolError, e:
198
 
            raise ConsoleError(*e.args)
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:
283
 
            execute['exception'] = cPickle.loads(execute['exception'])
284
 
        return execute
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