mii*_*kas 23 python linux subprocess pty unbuffered-output
我正在编写一个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)
Run Code Online (Sandbox Code Playgroud)
stdout
运行后,包含该程序的文件的内容将是:
Hello.
Type your name:
Nice to meet you, Anonymous!
Run Code Online (Sandbox Code Playgroud)
鉴于内容包含该文件stdin
是Anonymous<LF>
.所以,简而言之,对于给定的示例代码(以及,等效地,对于任何其他代码),我想实现如下结果:
Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!
Run Code Online (Sandbox Code Playgroud)
因此,问题是检测程序何时等待输入.
我尝试了以下方法来解决问题:
这允许父进程沿管道单独发送数据,但只能调用一次,因此不适用于具有多个输出和输入的程序 - 正如可以从文档中推断出来的那样.
文档警告不要这样,当程序开始等待输入时,Popen.stdout
s .read()
和.readline()
调用似乎无限地阻塞.
select.select(...)
,看是否文件句柄准备好I/O这似乎没有任何改善.显然,管道随时可供阅读或书写,因此在select.select(...)
这方面没有多大帮助.
作为建议这个答案,我试图创建一个单独的线程()是从阅读结果存储stdout
到一个队列() .要求用户输入的行之前的输出行很好地显示,但程序开始等待用户输入的行("Type your name: "
在上面的示例中)永远不会被读取.
按照这里的指示,我试图pty.openpty()
创建一个带有主文件和从文件描述符的伪终端.在那之后,我已经给奴隶的文件描述符作为参数subprocess.Popen(...)
调用的stdout
,stderr
和stdin
参数.读取打开的主文件描述符会os.fdopen(...)
产生与使用不同线程相同的结果:线路要求输入无法读取.
编辑:使用@Antti Haapala的pty.fork()
子进程创建示例而不是subprocess.Popen(...)
似乎允许我也读取由创建的输出raw_input(...)
.
我也试过了read()
,read_nonblocking()
和readline()
方法(记录在这里)的一种方法,使用Pexpect的催生,但最好的结果,我得到了read_nonblocking()
,是和以前一样:与输出线希望用户输入的东西不前得到阅读.与使用以下内容创建的PTY相同pty.fork()
:线要求输入确实被读取.
编辑:利用sys.stdout.write(...)
和sys.stdout.flush()
替代的print
荷兰国际集团在我的掌握程序,该程序创建的孩子,似乎解决提示行没有得到展示-它实际上得到了在这两种情况下阅读,虽然.
我也尝试过select.poll(...)
,但似乎管道或PTY主文件描述符总是可以写入.
read()
可以替换glibc 的系统调用包装器以将输入传递给主程序.但是,这不适用于静态链接或汇编程序.(虽然,现在我想起来了,任何这样的调用都可以从源代码中截获并替换为修补版本read()
- 可能仍然需要付出艰苦的努力.)read()
系统调用传递给程序可能是疯了......我认为PTY是可行的方式,因为它假装终端并且交互式程序在各个终端上运行.问题是,怎么样?
您是否注意到,如果stdout为terminal(isatty),raw_input会将提示字符串写入stderr; 如果stdout不是终端,那么提示也会写入stdout,但是stdout将处于完全缓冲模式.
随着stdout在tty上
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
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
因此,所有写入都同时被压缩到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)
Run Code Online (Sandbox Code Playgroud)
请注意,根据子程序设置的终端模式,可能会出现不同类型的换行符等.
现在关于"等待输入"问题,这不能真正帮助,因为人们总能写入伪终端; 字符将被放入缓冲区中等待.同样,管道总是允许在阻塞之前写入4K或32K或其他一些实现定义的数量.一种丑陋的方法是在程序进入读取系统调用时对程序进行检查并注意,fd = 0; 另一种方法是创建一个带有替换"read()"系统调用的C模块,并在glibc之前将其链接到动态链接器(如果可执行文件是静态链接或直接使用系统调用汇编程序,则会失败),以及然后每当执行read(0,...)系统调用时都会发出python信号.总而言之,可能完全不值得麻烦.
归档时间: |
|
查看次数: |
3440 次 |
最近记录: |