[pypy-svn] r45449 - in pypy/dist/pypy/translator/sandbox: . test

arigo at codespeak.net arigo at codespeak.net
Wed Aug 1 18:37:31 CEST 2007


Author: arigo
Date: Wed Aug  1 18:37:29 2007
New Revision: 45449

Added:
   pypy/dist/pypy/translator/sandbox/sandlib.py   (contents, props changed)
   pypy/dist/pypy/translator/sandbox/test/test_sandlib.py   (contents, props changed)
Modified:
   pypy/dist/pypy/translator/sandbox/sandboxmsg.py
Log:
Start a library meant for the parent process that runs a subprocess
that was translated with --sandbox.


Modified: pypy/dist/pypy/translator/sandbox/sandboxmsg.py
==============================================================================
--- pypy/dist/pypy/translator/sandbox/sandboxmsg.py	(original)
+++ pypy/dist/pypy/translator/sandbox/sandboxmsg.py	Wed Aug  1 18:37:29 2007
@@ -155,11 +155,58 @@
             raise ValueError
         return self.value[i:self.pos]
 
+    def decode(self, argtypes):
+        "NOT_RPYTHON"  # optimized decoder
+        v = self.value
+        i = self.pos
+        for t in argtypes:
+            if v[i] != t:
+                raise ValueError
+            end = i + 5
+            if t == "s":
+                length, = struct.unpack("!i", v[i+1:i+5])
+                end += length
+                yield v[i+5:end]
+            elif t == "i":
+                result, = struct.unpack("!i", v[i+1:end])
+                yield result
+            elif t == "I":
+                result, = struct.unpack("!I", v[i+1:end])
+                yield result
+            else:
+                raise ValueError
+            i = end
+        if i != len(v):
+            raise ValueError("more values to decode")
+
+def encode_message(types, values):
+    "NOT_RPYTHON"  # optimized encoder for messages
+    chars = ["!"]
+    entries = []
+    if len(types) != len(values):
+        raise ValueError("mismatch in the number of values to encode")
+    for t, val in zip(types, values):
+        chars.append("c")
+        entries.append(t)
+        if t == "s":
+            if not isinstance(val, str):
+                raise TypeError
+            chars.append("i%ds" % len(val))
+            entries.append(len(val))
+            entries.append(val)
+        elif t in "iI":
+            chars.append(t)
+            entries.append(val)
+        else:
+            raise ValueError
+    data = struct.pack(''.join(chars), *entries)
+    return struct.pack("!i", len(data) + 4) + data
+
 def timeout_read(f, size, timeout=None):
     if size < 0:
         raise ValueError("negative size")
     if timeout is None:
-        return f.read(size)
+        result = f.read(size)
     else:
         # XXX not Win32-compliant!
         assert not sys.platform.startswith('win'), "XXX fix me"
@@ -173,9 +220,11 @@
                     len(result), timeout, size))
             buf = os.read(fd, size - len(result))
             if not buf:
-                raise EOFError
+                break
             result += buf
-        return result
+    if len(result) < size:
+        raise EOFError
+    return result
 
 class Timeout(Exception):
     pass

Added: pypy/dist/pypy/translator/sandbox/sandlib.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/sandbox/sandlib.py	Wed Aug  1 18:37:29 2007
@@ -0,0 +1,110 @@
+"""
+A Python library to execute and communicate with a subprocess that
+was translated from RPython code with --sandbox.  This library is
+for the outer process, which can run CPython or PyPy.
+"""
+
+from py.compat import subprocess
+from pypy.translator.sandbox.sandboxmsg import Message, encode_message
+from pypy.translator.sandbox.sandboxmsg import read_message
+
+class SandboxedProc(object):
+    """Base class to control a sandboxed subprocess.
+    Inherit from this class and implement all the do_xxx() methods
+    for the external functions xxx that you want to support.
+    """
+    def __init__(self, args):
+        """'args' should a sequence of argument for the subprocess,
+        starting with the full path of the executable.
+        """
+        self.popen = subprocess.Popen(args,
+                                      stdin=subprocess.PIPE,
+                                      stdout=subprocess.PIPE)
+
+    def poll(self):
+        return self.popen.poll()
+
+    def wait(self):
+        return self.popen.wait()
+
+    def handle_forever(self):
+        while True:
+            try:
+                msg = read_message(self.popen.stdout)
+            except EOFError, e:
+                break
+            answer = self.handle_message(msg)
+            self.popen.stdin.write(answer)
+        returncode = self.popen.wait()
+        if returncode != 0:
+            raise OSError("the sandboxed subprocess exited with code %d" % (
+                returncode,))
+
+    def handle_message(self, msg):
+        fn = msg.nextstring()
+        try:
+            argtypes, restypes = self.TYPES[fn]
+        except KeyError:
+            raise IOError("trying to invoke unknown external function %r" % (
+                fn,))
+        handler = getattr(self, 'do_' + fn)
+        answers = handler(*msg.decode(argtypes))
+        if len(restypes) == 0:
+            assert answers is None
+            answers = (0,)
+        elif len(restypes) == 1:
+            answers = (0, answers)
+        else:
+            answers = (0,) + answers
+        return encode_message("i" + restypes, answers)
+
+    TYPES = {
+        "open": ("sii", "i"),
+        "read": ("iI", "s"),
+        "write": ("is", "I"),
+        "close": ("i", "i"),
+        }
+
+
+class SimpleIOSandboxedProc(SandboxedProc):
+    """Control a sandboxed subprocess which is only allowed to read from
+    its stdin and write to its stdout and stderr.
+    """
+    _input = None
+    _output = None
+    _error = None
+
+    def communicate(self, input=None):
+        """Send data to stdin. Read data from stdout and stderr,
+        until end-of-file is reached. Wait for process to terminate.
+        """
+        import cStringIO
+        if input:
+            if isinstance(input, str):
+                input = cStringIO.StringIO(input)
+            self._input = input
+        self._output = cStringIO.StringIO()
+        self._error = cStringIO.StringIO()
+        self.handle_forever()
+        output = self._output.getvalue()
+        self._output = None
+        error = self._error.getvalue()
+        self._error = None
+        return (output, error)
+
+    def do_read(self, fd, size):
+        if fd == 0:
+            if self._input is None:
+                return ""
+            else:
+                return self._input.read(size)
+        raise OSError("trying to read from fd %d" % (fd,))
+
+    def do_write(self, fd, data):
+        if fd == 1:
+            self._output.write(data)
+            return len(data)
+        if fd == 2:
+            self._error.write(data)
+            return len(data)
+        raise OSError("trying to write to fd %d" % (fd,))

Added: pypy/dist/pypy/translator/sandbox/test/test_sandlib.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/sandbox/test/test_sandlib.py	Wed Aug  1 18:37:29 2007
@@ -0,0 +1,79 @@
+import os, StringIO
+from pypy.tool.sourcetools import func_with_new_name
+from pypy.translator.sandbox.sandlib import SandboxedProc
+from pypy.translator.sandbox.sandlib import SimpleIOSandboxedProc
+from pypy.translator.interactive import Translation
+
+
+class MySandboxedProc(SandboxedProc):
+
+    def __init__(self, args, expected):
+        SandboxedProc.__init__(self, args)
+        self.expected = expected
+        self.seen = 0
+
+    def _make_method(name):
+        def do_xxx(self, *input):
+            print "decoded from subprocess: %s%r" % (name, input)
+            expectedmsg, expectedinput, output = self.expected[self.seen]
+            assert name == expectedmsg
+            assert input == expectedinput
+            self.seen += 1
+            return output
+        return func_with_new_name(do_xxx, 'do_%s' % name)
+
+    do_open  = _make_method("open")
+    do_read  = _make_method("read")
+    do_write = _make_method("write")
+    do_close = _make_method("close")
+
+
+def test_lib():
+    def entry_point(argv):
+        fd = os.open("/tmp/foobar", os.O_RDONLY, 0777)
+        assert fd == 77
+        res = os.read(fd, 123)
+        assert res == "he\x00llo"
+        count = os.write(fd, "world\x00!\x00")
+        assert count == 42
+        for arg in argv:
+            count = os.write(fd, arg)
+            assert count == 61
+        os.close(fd)
+        return 0
+    t = Translation(entry_point, backend='c', standalone=True, sandbox=True)
+    exe = t.compile()
+
+    proc = MySandboxedProc([exe, 'x1', 'y2'], expected = [
+        ("open", ("/tmp/foobar", os.O_RDONLY, 0777), 77),
+        ("read", (77, 123), "he\x00llo"),
+        ("write", (77, "world\x00!\x00"), 42),
+        ("write", (77, exe), 61),
+        ("write", (77, "x1"), 61),
+        ("write", (77, "y2"), 61),
+        ("close", (77,), 0),
+        ])
+    proc.handle_forever()
+    assert proc.seen == len(proc.expected)
+
+def test_simpleio():
+    def entry_point(argv):
+        print "Please enter a number:"
+        buf = ""
+        while True:
+            t = os.read(0, 1)    # 1 character from stdin
+            if not t:
+                raise EOFError
+            if t == '\n':
+                break
+            buf += t
+        num = int(buf)
+        print "The double is:", num * 2
+        return 0
+    t = Translation(entry_point, backend='c', standalone=True, sandbox=True)
+    exe = t.compile()
+
+    proc = SimpleIOSandboxedProc([exe, 'x1', 'y2'])
+    output, error = proc.communicate("21\n")
+    assert output == "Please enter a number:\nThe double is: 42\n"
+    assert error == ""


More information about the pypy-svn mailing list