#php #linux #terminal #tty #pty
#php #linux #терминал #tty #pty
Вопрос:
Описание проблемы
Я хотел бы открыть псевдотерминал Linux с помощью PHP, но, похоже, простого способа сделать это нет. Я экспериментировал с различными решениями, но ни одно из них не кажется достаточно хорошим. Целью PTY является эмуляция терминала с возможностью безупречного взаимодействия с такими программами, как zsh
и sudo
. Другие языки программирования, включая Python и C, имеют функции или библиотеки для этого. В Python есть библиотека PTY, которая может просто выполнять pty.spawn("/bin/zsh")
, а в C есть openpty()
функция.
Моя идеальная конечная цель — иметь функцию PHP, которая позволяет мне читать и записывать из / в терминал PTY и не требует установки внешних библиотек. (Многие провайдеры общего хостинга этого не допускают.)
Что я пробовал до сих пор
Использование proc_open()
Моей первоначальной идеей было просто использовать функцию proc_open()
PHP для создания терминала Bash с stdin
stdout
помощью и stderr
каналов (на основе примера # 1 в документации PHP) Это, однако, вскоре оказалось проблематичным, потому что на самом деле это не настоящий PTY. stty -a
Ошибка при запуске stty: stdin isn't a terminal
. Вот инструкции по воспроизведению этого.
- Запустите это с помощью
php pty_test.php
- Прочитайте вывод оболочки с
cat /tmp/stdout
помощью . - Вводите команды с
> /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
Я заметил, что запуск python3 -c "import pty;pty.spawn('/bin/bash');"
в оболочке, отличной от pty (которую я описал выше), приведет к созданию полностью интерактивной оболочки PTY, как я и хотел. Это привело к получению наполовину хорошего решения: установка $shell
переменной быть python3 -c "import pty;pty.spawn('/bin/bash')"
приведет к появлению интерактивной оболочки с использованием Python3. Но полагаться на внешнее программное обеспечение не идеально, поскольку наличие Python3 не всегда гарантировано. (И это решение также кажется слишком хакерским …)
/dev/ptmx
Я читал исходный код proc_open()
функции, также нашел источник для openpty()
. К сожалению, PHP не может напрямую вызывать эту функцию, но, возможно, можно воспроизвести ее поведение. Я мог fopen("/dev/ptmx","r ")
бы создать новый подчиненный, но openpty()
также использует grantpt()
и unlockpt()
, которые недоступны в PHP.
Интерфейс внешней функции FFI
позволяет получить доступ к внешним библиотекам. Может быть, можно было бы импортировать pty.h
и запустить openpty()
. К сожалению, FFI является очень экспериментальным и не всегда может быть доступен.
TL; DR
Какой самый безопасный и надежный способ создать PTY с использованием PHP?
Комментарии:
1. А как насчет expect_popen() ? Я знаю, что это зависит от расширения PECL, но на всякий случай…
Ответ №1:
Вам не обязательно использовать FFI для написания разделяемой библиотеки PHP.
Я только что попытался написать библиотеку PHP с открытым исходным кодом для этой цели. Я назвал его TeaOpenPTY. Я думаю, это может быть хорошим примером того, как написать простую библиотеку PHP на C.
- Репозиторий GitHub: https://github.com/ammarfaizi2/TeaOpenPTY
- Предварительно скомпилированная общая библиотека: https://github.com/ammarfaizi2/TeaOpenPTY/raw/master/compiled/tea_openpty.so
Как использовать библиотеку TeaOpenPTY?
Файл test.php
<?php
use TeaOpenPTYTeaOpenPTY;
$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$
Комментарии:
1. Спасибо за ваш вклад, но я пока не собираюсь отмечать его как решение, поскольку я ищу решение, которое не требует внешних библиотек. Я обязательно отредактирую свой первоначальный вопрос, чтобы прояснить это.