-
Notifications
You must be signed in to change notification settings - Fork 1
/
tr_sandbox.r2py
352 lines (272 loc) · 9.83 KB
/
tr_sandbox.r2py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
"""
<Program Name>
tr_sandbox.r2py
<Started>
March, 2011
<Author>
Lukas Puehringer, University of Vienna
lukas.puehringer@univie.ac.at
"""
class Sandbox:
"""
<Purpose>
A Sandbox is an isolated space that each user receives when requesting
the Try Repy webinterface. Each Sandbox has its own context to evaluate
Repy code using VitualNamespaces and some logging facilities.
Additionally the Sandbox wraps used Repy functions, mainly in order to isolate
it from other Sandboxes.
<Example Use>
Creation
Sandbox(user_id, context_dictionary, global_runtime)
Evaluation
Sandbox.evaluate(code, callargs)
Retrieve Current Log Buffer
Sandbox.getLog()
...
"""
def __init__(self, user_id, context, global_runtime):
"""
<Purpose>
Sandbox Constructor
It wraps the Sandboxes Context Functions and instantiates
variables and locks for logging and thread accounting.
<Arguments>
user_id (string)
context (dict)
A copy of a clean context, in which global functions dedicated
to the webcontroller are not included.
global_runtime (float)
Used to wrap the Sandboxes runtime.
"""
self.user = user_id
self.context = context
# Prepare the sandbox context
self.context_wrap()
self.start_time = getruntime() - global_runtime
self.log = {}
self.log_lock = createlock()
self.tmp_logtime = ""
self.tmp_output_buffer = ""
self.lock_output_buffer = createlock()
# Used for thread accounting
self.thread_count = 0
self.thread_lock = createlock()
self.thread_accounting_lock = createlock()
def my_log(self, *args):
"""
<Purpose>
Override The Repy log function to safe the evaluation output
to a log dictionary and a current log buffer.
<Arguments>
*args
The output which would be normally written to stdout.
"""
self.log_lock.acquire(True)
for arg in args:
if arg != self:
self.log[self.tmp_logtime]["output"] += str(arg) + " "
self.write_output_buffer( (str(arg) + " ") )
self.log[self.tmp_logtime]["output"] += "\n"
self.write_output_buffer("\n")
self.log_lock.release()
def context_wrap(self):
"""
<Purpose>
This makes sure that a Sandbox context is completely isolated
from other contexti.
Per default a Context copy is a shallow copy. Here we make sure
that deeper layers of the Context do not contain any References
shared between the contexti.
Additionally we wrap most of the repy functions to perform according
to our custom needs.
"""
#Initialize clean mycontext dict and callargs list
self.context["mycontext"] = {}
self.context["callargs"] = []
##
# General wrappers
def wrapped_log(*args):
self.my_log(*args)
self.context["log"] = wrapped_log
def wrapped_getruntime():
return (getruntime() - self.start_time)
self.context["getruntime"] = wrapped_getruntime
# This prevents user from kill the entire Application.
def wrapped_exitall():
pass
self.context["exitall"] = wrapped_exitall
##
# File wrappers
def wrapped_listdir():
return tr_listdir(self.user)
self.context["listdir"] = wrapped_listdir
def wrapped_open(*args):
return tr_open(self.user, *args)
self.context["open"] = wrapped_open
def wrapped_removefile(*args):
return tr_removefile(self.user, *args)
self.context["removefile"] = wrapped_removefile
##
# Timer wrappers
def wrapped_settimer(time, callbackfunction, args):
self.thread_register()
def wrapped_callbackfunction(*args):
try:
callbackfunction(*args[0])
except Exception, e:
# What do I do with this exception?
# A caught exception should bring the programm down.
# But for the moment it is only printed to the Shell
# were the webcontroller is called.
error_message = "ThreadException: " + str(e) + \
"\nCaution: Other threads could still be running!"
self.my_log(error_message)
self.thread_deregister()
return settimer(time, wrapped_callbackfunction, [args])
self.context['settimer'] = wrapped_settimer
def wrapped_canceltimer(timerhandle):
ret = canceltimer(timerhandle)
if ret:
self.thread_deregister()
return ret
self.context['canceltimer'] = wrapped_canceltimer
##
# Network wrappers
def wrapped_stopcomm(commhandle):
ret = stopcomm(commhandle)
if ret:
self.thread_deregister()
return ret
self.context['stopcomm'] = wrapped_stopcomm
def wrapped_recvmess(localip, localport, function):
self.thread_register() # This registers the listening thread
def wrapped_callback_udp(remIP, remport, message, commhandle):
self.thread_register()
try:
function(remIP, remport, message, commhandle)
except Exception, e:
# What do I do with this exception?
# A caught exception should bring the programm down.
# But for the moment it is only printed to the Shell
# were the webcontroller is called.
error_message = "ThreadException: " + str(e) + \
"\nCaution: Other threads could still be running!"
self.my_log(error_message)
self.thread_deregister()
return recvmess(localip, localport, wrapped_callback_udp)
self.context['recvmess'] = wrapped_recvmess
def wrapped_waitforconn(ip, port, callbackfunction):
self.thread_register()
def wrapped_callback_tcp(remip, remport, so, thishandle, listenhandle):
self.thread_register()
try:
callbackfunction(remip, remport, so, thishandle, listenhandle)
except Exception, e:
# What do I do with this exception?
# A caught exception should bring the programm down.
# But for the moment it is only printed to the Shell
# were the webcontroller is called.
error_message = "ThreadException: " + str(e) + \
"\nCaution: Other threads could still be running!"
self.my_log(error_message)
log("Exception: " + str(e))
self.thread_deregister()
return waitforconn(ip, port, wrapped_callback_tcp)
self.context['waitforconn'] = wrapped_waitforconn
# This makes sure, that the _context dict Sandboxes context
# is not a reference shared with other Sandboxes contexti
self.context["_context"] = self.context
def evaluate_repy(self, user_code, user_callargs):
# Reset the current outputbuffer
self.tmp_output_buffer = ""
# Ceate new logentry with a timestamp as key
# This thimestamp is temporarily stored as attribute to access it
# in the loggin wrapper.
self.tmp_logtime = self.context["getruntime"]()
self.log[self.tmp_logtime] = {}
self.log[self.tmp_logtime]["code"] = user_code
self.log[self.tmp_logtime]["output"] = ""
self.context["callargs"] = user_callargs
try:
# Create a new VirtualNamespace for the code
# and evaluate it in the Sandboxes context.
tmp_virt = createvirtualnamespace(user_code, "tryrepy")
tmp_virt.evaluate(self.context)
except Exception, e:
# This message is written to the log and the output buffer.
error_message = "MainThreadException: " + str(e) + \
"\nCaution: Other threads could still be running!"
self.my_log(error_message)
# Wait, maybe a thread is about to register.
sleep(0.5)
# The last active not-main thread will release this lock.
# Until then, the main (this) thread is blocked.
if self.thread_count:
self.thread_lock.acquire(True)
self.thread_lock.release()
# Read the remaining output buffer.
# Nothing should be written to the buffer afterwards.
output = self.read_output_buffer()
# Return what is left of the output buffer.
return output
def read_output_buffer(self):
"""
<Purpose>
Safely empty the output buffer.
<Returns>
output (string)
The current output buffer.
"""
self.lock_output_buffer.acquire(True)
output = self.tmp_output_buffer
self.tmp_output_buffer = ""
self.lock_output_buffer.release()
return output
def write_output_buffer(self, data):
"""
<Purpose>
Safely write to the output buffer.
<Arguments>
data (string)
The output that would be usually written to stdout.
"""
self.lock_output_buffer.acquire(True)
self.tmp_output_buffer += data
self.lock_output_buffer.release()
def get_log(self):
"""
<Purpose>
Return the entire current Sandbox log.
XXX: Is this problematic? If it is only read.
XXX: There should be no race condition??
<Returns>
log_dict (dict)
"""
return self.log
def thread_register(self):
"""
<Purpose>
Safely account a new thread for the current evaluation.
If it is the first thread a lock is acquired.
The lock makes sure that the main thread waits until
all threads are terminated and not till then signals
that the entire evaluation has terminated.
"""
self.thread_accounting_lock.acquire(True)
if not self.thread_count:
self.thread_lock.acquire(True)
self.thread_count = self.thread_count + 1
self.thread_accounting_lock.release()
def thread_deregister(self):
"""
<Purpose>
Safely account that a thread of the current evaluation
has terminated. If it was the last thread besides the
main thread, the lock is released which means, the main
thread can signal terminated of the entire evaluation.
"""
self.thread_accounting_lock.acquire(True)
self.thread_count = self.thread_count - 1
if not self.thread_count:
self.thread_lock.release()
self.thread_accounting_lock.release()