【问题标题】:Best way to open a PTY with PHP使用 PHP 打开 PTY 的最佳方式
【发布时间】:2021-04-04 05:40:14
【问题描述】:

问题描述

我想用 PHP 打开一个 Linux 伪终端,但似乎没有简单的方法可以做到这一点。我尝试了不同的解决方案,但似乎都不够好。 PTY 的目标是模拟具有与zshsudo 等程序完美交互的能力的终端。包括 Python 和 C 在内的其他编程语言都有相应的函数或库。 Python 有PTY library 可以简单地做pty.spawn("/bin/zsh"),C 有openpty() 函数。

我理想的最终目标是拥有一个 PHP 函数,允许我在 PTY 终端中读写,并且不需要安装外部库。 (许多共享主机提供商不允许这样做。)

到目前为止我所尝试的

使用 proc_open()

我最初的想法是使用proc_open() PHP 函数创建一个带有stdinstdoutstderr 管道的Bash 终端(基于PHP documentation 中的示例#1)但是,很快就证明是有问题的,因为它实际上不是真正的 PTY。运行 stty -a 时出现 stty: stdin isn't a terminal 错误。以下是复制此内容的说明。

  1. 使用php pty_test.php 运行它
  2. 使用cat /tmp/stdout 读取shell 的输出。
  3. > /tmp/stdin输入命令。

这是我用于此目的的 PHP 代码:

<?php
/* pty_test.php */


ini_set('display_errors', 1);
ini_set('display_startup_ūūerrors', 1);
error_reporting(E_ALL);

define("STD_IN", 0);
define("STD_OUT", 1);
define("STD_ERR", 2);

set_time_limit(0);
umask(0);

$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = "/bin/sh -i ";
$stdin_fifo = "/tmp/stdin";
$stdout_fifo = "/tmp/stdout";

posix_mkfifo($stdin_fifo, 0644);
posix_mkfifo($stdout_fifo, 0644);
$resource_stdin = fopen($stdin_fifo, "rb+");
$resource_stdout = fopen($stdout_fifo, "wb+");

$descriptorspec = array(
   STD_IN => array("pipe", "rb"),
   STD_OUT => array("pipe", "wb"),
   STD_ERR => array("pipe", "wb")
);

$process = proc_open($shell, $descriptorspec, $pipes, null, $env = null);

stream_set_blocking($pipes[STD_IN], 0);
stream_set_blocking($pipes[STD_OUT], 0);
stream_set_blocking($pipes[STD_ERR], 0);
stream_set_blocking($resource_stdin, 0);
stream_set_blocking($resource_stdout, 0);

while (1) {

    $read_a = array($resource_stdin, $pipes[STD_OUT], $pipes[STD_ERR]);
    $num_changed_streams = stream_select($read_a, $write_a, $error_a, null);
    
    if (in_array($resource_stdin, $read_a)) {
        $input = fread($resource_stdin, $chunk_size);
        fwrite($pipes[STD_IN], $input);
    }
    if (in_array($pipes[STD_OUT], $read_a)) {
        $input = fread($pipes[STD_OUT], $chunk_size);
        fwrite($resource_stdout, $input);
    }
    if (in_array($pipes[STD_ERR], $read_a)) {
        $input = fread($pipes[STD_ERR], $chunk_size);
        fwrite($resource_stdout, $input);
    }
}

fclose($resource_stdin);
fclose($resource_stdout);
fclose($pipes[STD_IN]);
fclose($pipes[STD_OUT]);
fclose($pipes[STD_ERR]);
proc_close($process);
unlink($stdin_fifo);
unlink($stdout_fifo);

?> 

Python PTY

我注意到在非 pty shell(我在上面描述的)中运行 python3 -c "import pty;pty.spawn('/bin/bash');" 将产生一个完全交互式的 PTY shell,如我所愿。这导致了一个半好的解决方案:将 $shell 变量设置为 python3 -c "import pty;pty.spawn('/bin/bash')" 将使用 Python3 生成交互式 shell。但是依赖外部软件并不理想,因为并不总能保证拥有 Python3。 (而且这个解决方案也感觉太老套了......)

/dev/ptmx

我正在阅读proc_open()函数的source code,也找到了openpty()的来源。不幸的是,PHP 不能直接调用这个函数,但也许可以复制它的行为。 我可以fopen("/dev/ptmx","r+") 创建一个新的从站,但openpty() 也使用grantpt()unlockpt(),它们在PHP 中不可用。

外部函数接口 FFI 允许访问外部库。也许可以导入pty.h 并运行openpty()。不幸的是,FFI 是非常实验性的,可能并不总是可用。

TL;DR

使用 PHP 生成 PTY 的最安全、最可靠的方法是什么?

【问题讨论】:

  • expect_popen() 怎么样?我知道这取决于 PECL 扩展,但以防万一……

标签: php linux terminal tty pty


【解决方案1】:

您不必使用 FFI 来编写 PHP 共享库。

我只是尝试为此目的编写一个开源 PHP 库。我将它命名为 TeaOpenPTY。我认为这是一个很好的例子,如何用 C 编写一个简单的 PHP 库。



如何使用 TeaOpenPTY 库?

文件 test.php

<?php

use TeaOpenPTY\TeaOpenPTY;

$app  = "/usr/bin/bash";
$argv = [$app, "-i"];

$teaOpenPTY = new TeaOpenPTY($app);

echo "Starting TeaOpenPTY...\n";

$ret = $teaOpenPTY->exec(...$argv);

if ($ret === -1) {
  echo "Error: ", $teaOpenPTY->error(), "\n";
}

echo "TeaOpenPTY terminated!\n";

运行

ammarfaizi2@integral:/tmp$ wget https://github.com/ammarfaizi2/TeaOpenPTY/raw/master/compiled/tea_openpty.so
[...output abbreviated...]
2020-12-28 14:39:20 (612 KB/s) - ‘tea_openpty.so’ saved [19048/19048]

ammarfaizi2@integral:/tmp$ echo $$ # Show the current bash PID
19068
ammarfaizi2@integral:/tmp$ php -d extension=$(pwd)/tea_openpty.so test.php
Starting TeaOpenPTY...
ammarfaizi2@integral:/tmp$ echo $$ # Now we are in the terminal spawned by tea_openpty
329423
ammarfaizi2@integral:/tmp$ stty -a
speed 38400 baud; rows 46; columns 192; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc
ammarfaizi2@integral:/tmp$ exit # Terminate the terminal
exit
TeaOpenPTY terminated!
ammarfaizi2@integral:/tmp$ echo $$
19068
ammarfaizi2@integral:/tmp$ 

【讨论】:

  • 感谢您的贡献,但我尚未将其标记为解决方案,因为我正在寻找不需要外部库的解决方案。我将确保编辑我原来的问题以澄清这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多