2
# Copyright (C) 2007-2008 The University of Melbourne
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.
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.
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
19
# Author: Matt Giuca, Tom Conway, David Coles (refactor)
22
# Mainly refactored out of consoleservice
35
from common import (chat, util)
38
trampoline_path = os.path.join(conf.ivle_install_dir, "bin/trampoline")
40
python_path = "/usr/bin/python"
41
console_dir = "/opt/ivle/scripts"
42
console_path = "/opt/ivle/scripts/python-console"
44
class ConsoleError(Exception):
45
""" The console failed in some way. This is bad. """
46
def __init__(self, value):
49
return repr(self.value)
51
class ConsoleException(Exception):
52
""" The code being exectuted on the console returned an exception.
54
def __init__(self, value):
57
return repr(self.value)
59
class Console(object):
60
""" Provides a nice python interface to the console
62
def __init__(self, uid, jail_path, working_dir):
63
"""Starts up a console service for user uid, inside chroot jail
64
jail_path with work directory of working_dir
67
self.jail_path = jail_path
68
self.working_dir = working_dir
72
# TODO: Check if we are already running a console. If we are shut it
75
# TODO: Figure out the host name the console server is running on.
76
self.host = socket.gethostname()
80
self.magic = md5.new(uuid.uuid4().bytes).digest().encode('hex')
82
# Try to find a free port on the server.
83
# Just try some random ports in the range [3000,8000)
84
# until we either succeed, or give up. If you think this
85
# sounds risky, it isn't:
86
# For N ports (e.g. 5000) with k (e.g. 100) in use, the
87
# probability of failing to find a free port in t (e.g. 5) tries
88
# is (k / N) ** t (e.g. 3.2*10e-9).
92
self.port = int(random.uniform(3000, 8000))
94
# Start the console server (port, magic)
95
# trampoline usage: tramp uid jail_dir working_dir script_path args
96
# console usage: python-console port magic
117
# If we can't start the console after 5 attemps (can't find a free port
118
# during random probing, syntax errors, segfaults) throw an exception.
120
raise ConsoleError("Unable to start console service!")
122
def __chat(self, cmd, args):
123
""" A wrapper around chat.chat to comunicate directly with the
127
response = chat.chat(self.host, self.port,
128
{'cmd': cmd, 'text': args}, self.magic)
129
except socket.error, (enumber, estring):
130
if enumber == errno.ECONNREFUSED:
133
"Could not establish a connection to the python console")
135
# Some other error - probably serious
136
raise socket.error, (enumber, estring)
137
except cjson.DecodeError:
138
# Couldn't decode the JSON
140
"Could not understand the python console response")
142
# Look for user errors
143
if 'exc' in response:
144
raise ConsoleException(response['exc'])
148
def chat(self, code=''):
149
""" Executes a partial block of code """
150
return self.__chat('chat', code)
152
def block(self, code):
153
""" Executes a block of code and returns the output """
154
block = self.__chat('block', code)
155
if 'output' in block:
156
return block['output']
157
elif 'okay' in block:
160
raise ConsoleException("Bad response from console: %s"%str(block))
162
def flush(self, globs=None):
163
""" Resets the consoles globals() to the default and optionally augment
164
them with a dictionary simple globals. (Must be able to be pickled)
168
if globs is not None:
170
pickled_globs[g] = cPickle.dumps(globs[g])
172
flush = self.__chat('flush', pickled_globs)
173
if 'response' in flush and flush['response'] == 'okay':
176
raise ConsoleError("Bad response from console: %s"%str(flush))
178
def call(self, function, *args, **kwargs):
179
""" Calls a function in the python console. Can take in a list of
180
repr() args and dictionary of repr() values kwargs. These will be
181
evaluated on the server side.
184
'function': function,
187
call = self.__chat('call', call_args)
189
# Unpickle any exceptions
190
if 'exception' in call:
191
call['exception']['except'] = \
192
cPickle.loads(call['exception']['except'])
196
def inspect(self, code=''):
197
""" Runs a block of code in the python console returning a dictionary
198
summary of the evaluation. Currently this includes the values of
199
stdout, stderr, simple global varibles.
200
If an exception was thrown then this dictionary also includes a
201
exception dictionary containg a traceback string and the exception
204
inspection = self.__chat('inspect', code)
206
# Unpickle the globals
207
for g in inspection['globals']:
208
inspection['globals'][g] = cPickle.loads(inspection['globals'][g])
210
# Unpickle any exceptions
211
if 'exception' in inspection:
212
inspection['exception']['except'] = \
213
cPickle.loads(inspection['exception']['except'])
217
def set_vars(self, variables):
218
""" Takes a dictionary of varibles to add to the console's global
219
space. These are evaluated in the local space so you can't use this to
220
set a varible to a value to be calculated on the console side.
224
vars[v] = repr(variables[v])
226
set_vars = self.__chat('set_vars', vars)
228
if set_vars.get('response') != 'okay':
229
raise ConsoleError("Could not set variables")
232
""" Causes the console process to terminate """
233
self.__chat('restart', None)