我正在编写一个Python程序,用于在Linux服务器上运行用户上传的任意代码(因此,在最坏的情况下,就是不安全,错误和崩溃的代码)。除了安全性问题外,我的目标是确定代码(可能以任何语言编写,编译或解释的)是否将正确的内容写入stdout,stderr以及是否将给定输入的其他文件写入程序的stdin。之后,我需要向用户显示结果。
stdout
stderr
stdin
目前,我的解决办法是使用产卵子进程subprocess.Popen(...)与文件句柄stdout,stderr和stdin。后面的文件stdin句柄包含了操作过程中的程序读取输入,并且该程序已终止后,将stdout和stderr文件的读取,并检查正确性。
subprocess.Popen(...)
这种方法在其他方面可以完美地起作用,但是当我显示结果时,我无法组合给定的输入和输出,因此输入将出现在与从终端运行程序时相同的位置。即对于像这样的程序
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>。因此,简而言之,对于给定的示例代码(以及等效的 任何 其他代码),我想要实现如下结果:
Anonymous<LF>
Hello. Type your name: Anonymous Nice to meet you, Anonymous!
因此,问题在于检测程序何时等待输入。
我尝试了以下方法来解决问题:
Popen.communicate(…)
这允许父进程沿着管道单独发送数据,但是只能被调用一次,因此不适合具有多个输出和输入的程序- 正如可以从文档中推断出的那样。
直接从Popen.stdout和Popen.stderr读取并写入Popen.stdin
文档对此提出警告,并且在程序开始等待输入时,Popen.stdouts.read()和.readline()调用似乎无限阻塞。
Popen.stdout
.read()
.readline()
使用select.select(...),看是否文件句柄准备好I / O
select.select(...)
这似乎没有任何改善。显然,管道始终可以读取或写入,因此select.select(...)在这里没有太大帮助。
如该答案所建议,我尝试创建一个单独的Thread()来存储从读取stdout到Queue()的结果。要求用户输入的行之前的输出行显示得很好,但是程序开始等待用户输入的行("Type your name: "在上面的示例中)从未被读取。
"Type your name: "
按照这里的指示,我试图pty.openpty()用主文件和从文件描述符创建一个伪终端。在那之后,我已经给奴隶的文件描述符作为参数subprocess.Popen(...)调用的stdout,stderr和stdin参数。读取以打开的主文件描述符os.fdopen(...)产生的结果与使用不同线程的结果相同:要求输入的行不会被读取。
pty.openpty()
os.fdopen(...)
编辑: 使用@Antti Haapala的示例来pty.fork()创建子进程,而不是subprocess.Popen(...)让我也阅读了创建的输出raw_input(...)。
pty.fork()
raw_input(...)
我也试过了read(),read_nonblocking()和readline()方法(记录在这里)与Pexpect的催生了一个过程的,但最好的结果,我用了read_nonblocking(), 是和以前一样:与输出线希望用户输入的东西不前阅读。 相同与创建的PTY pty.fork():苛刻的输入行 并 得到读。
read()
read_nonblocking()
readline()
编辑: 利用sys.stdout.write(...)和sys.stdout.flush()替代的print荷兰国际集团在我的 掌握 程序,该程序创建的孩子,似乎解决提示行没有得到展示-它实际上得到了在这两种情况下阅读,虽然。
sys.stdout.write(...)
sys.stdout.flush()
print
我也尝试过select.poll(...),但是似乎管道或PTY主文件描述符总是可以编写。
select.poll(...)
我认为PTY是必经之路,因为它伪造了一个终端,并且交互式程序在各处的终端上运行。问题是,如何?
您是否已经注意到,如果stdout是terminal(isatty),则raw_input将提示字符串写入stderr;如果stdout不是终端,那么提示符也会写入stdout,但是stdout将处于完全缓冲模式。
在tty上使用stdout
write(1, "Hello.\n", 7) = 7 ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 write(2, "Type your name: ", 16) = 16 fstat(0, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb114059000 read(0, "abc\n", 1024) = 4 write(1, "Nice to meet you, abc!\n", 23) = 23
使用stdout不在tty上
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff8d9d3410) = -1 ENOTTY (Inappropriate ioctl for device) # oops, python noticed that stdout is NOTTY. fstat(0, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29895f0000 read(0, "abc\n", 1024) = 4 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f29891c4bd0}, {0x451f62, [], SA_RESTORER, 0x7f29891c4bd0}, 8) = 0 write(1, "Hello.\nType your name: Nice to m"..., 46) = 46 # squeeze all output at the same time into stdout... pfft.
因此,所有写入都同时被压缩到stdout中。读取输入后的情况更糟。
因此,真正的解决方案是使用pty。但是,您做错了。为了使pty起作用,必须使用pty.fork()命令而不是子进程。(这将非常棘手)。我有一些这样的工作代码:
import os import tty import pty program = "python" # command name in argv[0] argv = [ "python", "foo.py" ] pid, master_fd = pty.fork() # we are in the child process if pid == pty.CHILD: # execute the program os.execlp(program, *argv) # else we are still in the parent, and pty.fork returned the pid of # the child. Now you can read, write in master_fd, or use select: # rfds, wfds, xfds = select.select([master_fd], [], [], timeout)
请注意,根据子程序设置的终端模式,可能会有不同类型的换行符等。
现在有关“等待输入”的问题,由于总是可以写入伪终端,因此无法真正解决。字符将在缓冲区中等待。同样,在阻塞之前,管道始终允许写入多达4K或32K或其他一些实现定义的数量。一种丑陋的方法是跟踪程序,并在程序进入读取系统调用时注意到它,fd = 0; 另一种方法是使用替换的“ read()”系统调用制作一个C模块,并在动态链接程序的glibc之前将其链接(如果可执行文件是静态链接的,或者直接通过汇编程序使用系统调用则失败…),并且然后将在执行read(0,…)系统调用时向python发送信号。总而言之,可能完全不值得麻烦。