[pypy-svn] r46391 - pypy/dist/pypy/translator/sandbox
arigo at codespeak.net
arigo at codespeak.net
Fri Sep 7 13:34:07 CEST 2007
Author: arigo
Date: Fri Sep 7 13:34:07 2007
New Revision: 46391
Modified:
pypy/dist/pypy/translator/sandbox/pypy_interact.py
pypy/dist/pypy/translator/sandbox/sandlib.py
Log:
A --timeout option for pypy_interact.py.
Modified: pypy/dist/pypy/translator/sandbox/pypy_interact.py
==============================================================================
--- pypy/dist/pypy/translator/sandbox/pypy_interact.py (original)
+++ pypy/dist/pypy/translator/sandbox/pypy_interact.py Fri Sep 7 13:34:07 2007
@@ -11,6 +11,7 @@
--heapsize=N limit memory usage to N bytes, or kilo- mega- giga-bytes
with the 'k', 'm' or 'g' suffix respectively.
ATM this only works if the sandboxed executable uses Boehm.
+ --timeout=N limit execution time to N (real-time) seconds.
"""
import sys, os
@@ -57,8 +58,9 @@
if __name__ == '__main__':
from getopt import getopt # and not gnu_getopt!
options, arguments = getopt(sys.argv[1:], 't:h',
- ['tmp=', 'heapsize=', 'help'])
+ ['tmp=', 'heapsize=', 'timeout=', 'help'])
tmpdir = None
+ timeout = None
extraoptions = []
def help():
@@ -86,6 +88,8 @@
if bytes > sys.maxint:
raise OverflowError("--heapsize maximum is %d" % sys.maxint)
extraoptions[:0] = ['--heapsize', str(bytes)]
+ elif option == '--timeout':
+ timeout = int(value)
elif option in ['-h', '--help']:
help()
else:
@@ -96,4 +100,6 @@
sandproc = PyPySandboxedProc(arguments[0], extraoptions + arguments[1:],
tmpdir=tmpdir)
+ if timeout is not None:
+ sandproc.settimeout(timeout)
sandproc.interact()
Modified: pypy/dist/pypy/translator/sandbox/sandlib.py
==============================================================================
--- pypy/dist/pypy/translator/sandbox/sandlib.py (original)
+++ pypy/dist/pypy/translator/sandbox/sandlib.py Fri Sep 7 13:34:07 2007
@@ -5,7 +5,8 @@
"""
import py
-import marshal, sys, os, posixpath, errno, stat, time
+import sys, os, posixpath, errno, stat, time
+from pypy.lib import marshal # see below
from pypy.rpython.module.ll_os_stat import s_StatResult
from pypy.tool.ansi_print import AnsiLog
from pypy.rlib.rarithmetic import r_longlong
@@ -23,6 +24,13 @@
py.log.setconsumer("sandlib", MyAnsiLog())
+# Note: we use pypy.lib.marshal instead of the built-in marshal
+# for two reasons. The built-in module could be made to segfault
+# or be attackable in other ways by sending malicious input to
+# load(). Also, marshal.load(f) blocks with the GIL held when
+# f is a pipe with no data immediately avaialble, preventing the
+# _waiting_thread to run.
+
def read_message(f, timeout=None):
# warning: 'timeout' is not really reliable and should only be used
# for testing. Also, it doesn't work if the file f does any buffering.
@@ -106,12 +114,62 @@
stdout=subprocess.PIPE,
close_fds=True,
env={})
+ self.popenlock = None
+ self.currenttimeout = None
+
+ def withlock(self, function, *args, **kwds):
+ lock = self.popenlock
+ if lock is not None:
+ lock.acquire()
+ try:
+ return function(*args, **kwds)
+ finally:
+ if lock is not None:
+ lock.release()
+
+ def settimeout(self, timeout):
+ """Start a timeout that will kill the subprocess after the given
+ amount of time. Only one timeout can be active at a time.
+ """
+ import thread
+ from pypy.tool.killsubprocess import killsubprocess
+
+ def _waiting_thread():
+ while True:
+ t = self.currenttimeout
+ if t is None:
+ return # cancelled
+ delay = t - time.time()
+ if delay <= 0.0:
+ break # expired!
+ time.sleep(min(delay*1.001, 1))
+ self.withlock(killsubprocess, self.popen)
+
+ def _settimeout():
+ need_new_thread = self.currenttimeout is None
+ self.currenttimeout = time.time() + timeout
+ if need_new_thread:
+ thread.start_new_thread(_waiting_thread, ())
+
+ if self.popenlock is None:
+ self.popenlock = thread.allocate_lock()
+ self.withlock(_settimeout)
+
+ def canceltimeout(self):
+ """Cancel the current timeout."""
+ self.currenttimeout = None
def poll(self):
- return self.popen.poll()
+ returncode = self.withlock(self.popen.poll)
+ if returncode is not None:
+ self.canceltimeout()
+ return returncode
def wait(self):
- return self.popen.wait()
+ returncode = self.withlock(self.popen.wait)
+ if returncode is not None:
+ self.canceltimeout()
+ return returncode
def handle_forever(self):
returncode = self.handle_until_return()
@@ -120,20 +178,22 @@
returncode,))
def handle_until_return(self):
+ child_stdin = self.popen.stdin
+ child_stdout = self.popen.stdout
if self.os_level_sandboxing and sys.platform.startswith('linux2'):
# rationale: we wait until the child process started completely,
# letting the C library do any system calls it wants for
# initialization. When the RPython code starts up, it quickly
# does its first system call. At this point we turn seccomp on.
import select
- select.select([self.popen.stdout], [], [])
+ select.select([child_stdout], [], [])
f = open('/proc/%d/seccomp' % self.popen.pid, 'w')
print >> f, 1
f.close()
while True:
try:
- fnname = read_message(self.popen.stdout)
- args = read_message(self.popen.stdout)
+ fnname = read_message(child_stdout)
+ args = read_message(child_stdout)
except EOFError, e:
break
if self.debug:
@@ -143,7 +203,7 @@
answer, resulttype = self.handle_message(fnname, *args)
except Exception, e:
tb = sys.exc_info()[2]
- write_exception(self.popen.stdin, e, tb)
+ write_exception(child_stdin, e, tb)
if self.debug:
if str(e):
log.exception('%s: %s' % (e.__class__.__name__, e))
@@ -152,10 +212,10 @@
else:
if self.debug:
log.result(shortrepr(answer))
- write_message(self.popen.stdin, 0) # error code - 0 for ok
- write_message(self.popen.stdin, answer, resulttype)
- self.popen.stdin.flush()
- returncode = self.popen.wait()
+ write_message(child_stdin, 0) # error code - 0 for ok
+ write_message(child_stdin, answer, resulttype)
+ child_stdin.flush()
+ returncode = self.wait()
return returncode
def handle_message(self, fnname, *args):
More information about the Pypy-commit
mailing list