我正在编写一个 Python 程序,用于在 Linux 服务器上运行用户上传的任意(因此,在最坏的情况下,不安全、错误和崩溃)代码。除了安全问题之外,我的目标是确定代码(可能是任何语言,编译或解释)是否将正确的stdout
内容stderr
写入stdin
. 在此之后,我需要向用户显示结果。
目前的解决方案
目前,我的解决方案是subprocess.Popen(...)
使用stdout
,stderr
和stdin
. 句柄后面的文件stdin
包含程序在运行期间读取的输入,在程序终止后,将读取stdout
和stderr
文件并检查其正确性。
问题
这种方法在其他方面效果很好,但是当我显示结果时,我无法组合给定的输入和输出,以便输入出现在与从终端运行程序时相同的位置。即对于像这样的程序
print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)
包含程序的文件的内容stdout
在运行后将是:
Hello.
Type your name:
Nice to meet you, Anonymous!
鉴于包含 的文件的内容stdin
是Anonymous<LF>
. 因此,简而言之,对于给定的示例代码(以及等效地,对于任何其他代码),我希望获得如下结果:
Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!
因此,问题在于检测程序何时等待输入。
尝试过的方法
我尝试了以下方法来解决问题:
Popen.communicate(...)
这允许父进程沿管道单独发送数据,但只能调用一次,因此不适合具有多个输出和输入的程序 - 正如可以从文档中推断的那样。
直接从Popen.stdout和Popen.stderr读取并写入Popen.stdin
文档对此提出警告,当程序开始等待输入时,Popen.stdout
s.read()
和调用似乎无限阻塞。.readline()
用于select.select(...)
查看文件句柄是否已准备好进行 I/O
这似乎没有任何改善。显然管道总是准备好读取或写入,所以select.select(...)
在这里没有多大帮助。
使用不同的线程进行非阻塞读取
正如这个答案中所建议的,我尝试创建一个单独的Thread()来存储从读取stdout
到Queue()的结果。要求用户输入的行之前的输出行显示得很好,但程序开始等待用户输入的行("Type your name: "
在上面的示例中)永远不会被读取。
使用PTY slave 作为子进程的文件句柄
按照这里的指示,我尝试pty.openpty()
创建一个带有主从文件描述符的伪终端。之后,我给出了从属文件描述符作为subprocess.Popen(...)
调用的参数stdout
,stderr
和stdin
参数。读取打开的主文件描述符os.fdopen(...)
会产生与使用不同线程相同的结果:要求输入的行不会被读取。
编辑:使用@Antti Haapala 的pty.fork()
子进程创建示例而不是subprocess.Popen(...)
似乎允许我也阅读由raw_input(...)
.
使用pexpect
我还尝试了使用 pexpect 生成的进程的read()
,read_nonblocking()
和readline()
方法(在此处read_nonblocking()
记录),但是我得到的最佳结果与以前相同:在希望用户输入之前带有输出的行没有阅读。与使用创建的 PTY 相同:确实会读取pty.fork()
要求输入的行。
编辑:通过在我的创建子程序的主程序中使用sys.stdout.write(...)
andsys.stdout.flush()
而不是print
ing ,似乎修复了提示行未显示的问题——尽管它实际上在两种情况下都被读取了。
其他
我也试过select.poll(...)
,但似乎管道或 PTY 主文件描述符总是准备好写入。
笔记
其他解决方案
- 我还想到的是在一段时间过去而没有生成新输出的情况下尝试输入输入。然而,这是有风险的,因为无法知道程序是否正在执行繁重的计算。
- 正如@Antti Haapala 在他的回答中提到的那样,
read()
可以替换来自 glibc 的系统调用包装器以将输入传达给主程序。但是,这不适用于静态链接或汇编程序。(虽然,现在我想起来了,任何这样的调用都可以从源代码中截获并替换为read()
- 的修补版本可能仍然很难实现。) - 修改 Linux 内核代码以将
read()
系统调用传达给程序可能是疯狂的......
PTY
我认为 PTY 是要走的路,因为它伪造了一个终端,并且交互式程序在任何地方的终端上运行。问题是,怎么做?