python - 与沙盒 PyPy 解释器交互 - secondary .communicate() 返回空元组

标签 python sandbox pypy

我正在尝试创建一种在普通(非沙盒)cPython 或 PyPy 脚本中与沙盒 PyPy 解释器交互的方法。

我已经能够按照这些说明编译沙盒 PyPy 解释器 http://doc.pypy.org/en/latest/sandbox.html我有一个 pypy-c-sandbox 文件,它与 pypy_interact.py 一起创建交互式沙盒解释器。

现在我想做一件非常类似的事情,但我不想使用 stdin/stdout 作为我的 io,而是想使用 python 脚本与 pypy-sandbox 进程交互。我已经让它在很大程度上发挥作用了。我可以使用 .communicate() 函数和 cStringIO 对象作为输入、输出和错误,并从普通的 python 访问这些数据。

但是,这是我的问题,当我在同一个实例化的 PyPy 沙箱对象上第二次调用 .communicate() 时,我没有得到任何返回。这只是第一个 .communicate 的工作。我很困惑为什么会出现这种情况以及如何解决它。

我拼凑了一个丑陋的黑客来演示我的问题:

import sys, os
import autopath
from pypy.translator.sandbox.sandlib import SimpleIOSandboxedProc
from pypy.translator.sandbox.sandlib import VirtualizedSandboxedProc
from pypy.translator.sandbox.vfs import Dir, RealDir, RealFile
import pypy


LIB_ROOT = os.path.dirname(os.path.dirname(pypy.__file__))

class PyPySandboxedProc(VirtualizedSandboxedProc, SimpleIOSandboxedProc):
    argv0 = '/bin/pypy-c'
    virtual_cwd = '/tmp'
    virtual_env = {}
    virtual_console_isatty = True
    arguments = ['../goal/pypy-c', '-u']

    def __init__(self, executable, arguments, tmpdir=None, debug=True):
        self.executable = executable = os.path.abspath(executable)
        self.tmpdir = tmpdir
        self.debug = debug
        super(PyPySandboxedProc, self).__init__([self.argv0] + arguments,
                                                executable=executable)

    def build_virtual_root(self):
        # build a virtual file system:
        # * can access its own executable
        # * can access the pure Python libraries
        # * can access the temporary usession directory as /tmp
        exclude = ['.pyc', '.pyo']
        if self.tmpdir is None:
            tmpdirnode = Dir({})
        else:
            tmpdirnode = RealDir(self.tmpdir, exclude=exclude)
        libroot = str(LIB_ROOT)

        return Dir({
            'bin': Dir({
                'pypy-c': RealFile(self.executable),
                'lib-python': RealDir(os.path.join(libroot, 'lib-python'),
                                      exclude=exclude),
                'lib_pypy': RealDir(os.path.join(libroot, 'lib_pypy'),
                                      exclude=exclude),
                }),
             'tmp': tmpdirnode,
             })

# run test
arguments = ['../goal/pypy-c', '-u']

sandproc = PyPySandboxedProc(arguments[0], arguments[1:],
                             tmpdir=None, debug=True)

#start the proc
code1 = "print 'started'\na = 5\nprint a"
code2 = "b = a\nprint b\nprint 'code 2 was run'"

output, error = sandproc.communicate(code1)
print "output: %s\n error: %s\n" % (output, error)

output, error = sandproc.communicate(code2)
print "output: %s\n error: %s\n" % (output, error)

我真的很想以某种方式让 code2 由同一个 sandproc 实例运行,但让它的输入/输出单独返回。如果我将所有代码连接在一起并立即运行它,它可以工作,但是解析给定输入的输出会有点痛苦。

最佳答案

from rpython.translator.sandbox.sandlib import SimpleIOSandboxedProc

您正在 PyPySandboxedProc 中导入和扩展 SimpleIOSandboxedProc。在源代码( sandlib.py source )中,您将看到 sandproc.communicate() 发送数据并在子进程终止后返回。

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)

在上面的代码中,调用了 self.handle_forever():

def handle_until_return(self):
    child_stdin  = self.popen.stdin
    child_stdout = self.popen.stdout
    if self.os_level_sandboxing and sys.platform.startswith('linux'):
        # 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([child_stdout], [], [])
        f = open('/proc/%d/seccomp' % self.popen.pid, 'w')
        print >> f, 1
        f.close()
    while True:
        try:
            fnname = read_message(child_stdout)
            args   = read_message(child_stdout)
        except EOFError, e:
            break
        if self.log and not self.is_spam(fnname, *args):
            self.log.call('%s(%s)' % (fnname,
                                 ', '.join([shortrepr(x) for x in args])))
        try:
            answer, resulttype = self.handle_message(fnname, *args)
        except Exception, e:
            tb = sys.exc_info()[2]
            write_exception(child_stdin, e, tb)
            if self.log:
                if str(e):
                    self.log.exception('%s: %s' % (e.__class__.__name__, e))
                else:
                    self.log.exception('%s' % (e.__class__.__name__,))
        else:
            if self.log and not self.is_spam(fnname, *args):
                self.log.result(shortrepr(answer))
            try:
                write_message(child_stdin, 0)  # error code - 0 for ok
                write_message(child_stdin, answer, resulttype)
                child_stdin.flush()
            except (IOError, OSError):
                # likely cause: subprocess is dead, child_stdin closed
                if self.poll() is not None:
                    break
                else:
                    raise
    returncode = self.wait()
    return returncode

正如我们所看到的,这是一个“while True:”,这意味着这个函数不会返回,直到我们收到一个异常抛出。 因此,sandproc.communicate() 无法实现您希望实现的目标

您有两个选择。

硬选项是 fork 你的主进程。使用一个进程运行 sandproc.interact() 并传入一些 os.pipes。并使用另一个进程读取和写入所述管道。这是低效的,因为它需要 3 个进程(一个用于沙盒应用程序,一个用于主进程,一个用于 fork 进程)。

简单选项是重写 SimpleIOSandboxedProc 类中的一些函数。您所要做的就是在 PyPySandboxedProc 中重新实现这些函数。

def do_ll_os__ll_os_read(self, fd, size):
    if fd == 0:
        if self._input is None:
            return ""
        elif (getattr(self, 'virtual_console_isatty', False) or
              self._input.isatty()):
            # don't wait for all 'size' chars if reading from a tty,
            # to avoid blocking.  Instead, stop after reading a line.

            # For now, waiting at the interactive console is the
            # only time that counts as idle.
            self.enter_idle()
            try:
                inputdata = self._input.readline(size) #TODO: THIS IS WHERE YOU HANDLE READING FROM THE SANDBOXED PROCESS
            finally:
                self.leave_idle()
        else:
            inputdata = self._input.read(size)
        if self.inputlogfile is not None:
            self.inputlogfile.write(inputdata)
        return inputdata
    raise OSError("trying to read from fd %d" % (fd,))

def do_ll_os__ll_os_write(self, fd, data):
    if fd == 1:
        self._output.write(data) #TODO: THIS IS WHERE YOU WRITE TO THE SANDBOXED PROCESS
        return len(data)
    if fd == 2:
        self._error.write(data)
        return len(data)
    raise OSError("trying to write to fd %d" % (fd,))

#TODO:标签注释的行是从沙盒进程中注入(inject)和读取数据的地方。

您要做的就是实现逻辑,在其中写入一些数据(当沙盒进程读取时),等待响应(直到进程写入),写入更多数据,然后再次读取响应(当进程写入第二次)。

关于python - 与沙盒 PyPy 解释器交互 - secondary .communicate() 返回空元组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13769346/

相关文章:

python - 填充图像以在 wxpython 中使用

python - 如何在python中修改和枚举XML标签

iphone - 应用内购买错误

python - fatal error : 'openssl/e_os2.h' file not found in pypy

python - 使用 pypy 运行 Nose 测试

python - 在 Linux 上安装和运行 pypy

python - 如何在 beautifulsoup4 中根据图像内部的内容分离图像链接

Python 或 LibreOffice 保存使用密码加密的 xlsx 文件

macos - 沙盒 OS X 帮助程序应用程序无法打开主应用程序

ios - 如何使用 flutter 在 iOS 中测试验证 InAppPurchase