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