Лучший способ открыть PTY с помощью PHP

#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 . Вот инструкции по воспроизведению этого.

  1. Запустите это с помощью php pty_test.php
  2. Прочитайте вывод оболочки с cat /tmp/stdout помощью .
  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

Я заметил, что запуск 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.



Как использовать библиотеку 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. Спасибо за ваш вклад, но я пока не собираюсь отмечать его как решение, поскольку я ищу решение, которое не требует внешних библиотек. Я обязательно отредактирую свой первоначальный вопрос, чтобы прояснить это.