linux - Best way to open a PTY with PHP
Get the solution ↓↓↓Problem description
I would like to open a Linux pseudoterminal with PHP but it seems there is no simple way of doing that. I have experimented with different solutions but none seem to be good enough.
The goal of the PTY is to emulate a terminal with the capability of flawlessly interacting with programs such aszsh
andsudo
. Other programming languages including Python and C have functions or libraries for that. Python has the PTY library which can simply dopty.spawn("/bin/zsh")
, and C has the function.
My ideal end goal is to have a PHP function that allows me to read and write from/into the PTY terminal and does not require installing external libraries. (A lot of shared hosting providers do not allow that.)
What I have tried so far
Using proc_open()
My initial idea was just to use theproc_open()
PHP function to create a Bash terminal withstdin
,stdout
andstderr
pipes (Based on Example #1 in the PHP documentation) This, however, did soon prove to be problematic because it is actually not a real PTY. Runningstty -a
errored withstty: stdin isn't a terminal
. Here are the instructions for reproducing this.
- Run this with
php pty_test.php
- Read the output of the shell with
cat /tmp/stdout
. - Input commands with
> /tmp/stdin
.
Here is the PHP code which I used for this:
<?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
I noticed that runningpython3 -c "import pty;pty.spawn('/bin/bash');"
in the non-pty shell (which I described above) will result in a fully interactive PTY shell as I desired. This resulted in a half-good solution: setting the$shell
variable to bepython3 -c "import pty;pty.spawn('/bin/bash')"
will spawn the interactive shell using Python3. But relying on external software is not ideal since having Python3 is not always guaranteed. (And this solution also feels way too hacky...)
/dev/ptmx
I was reading the source code of theproc_open()
function also found the source foropenpty()
. Unfortunately, PHP can't directly call this function but perhaps it is possible to replicate the behavior of it.
I couldfopen("/dev/ptmx","r+")
to create a new slave butopenpty()
also usesgrantpt()
andunlockpt()
, which are not available in PHP.
Foreign Function Interface
FFI allows access to external libraries. Maybe it would be possible to importpty.h
and to runopenpty()
. Unfortunately, FFI is very experimental and may not always be available.
TL;DR
What is the safest and most reliable way to spawn a PTY using PHP?
Answer
Solution:
You do not have to use FFI to write PHP shared library.
I just tried to write an open source PHP library for this purpose. I named it TeaOpenPTY. I think this can be a good example how to write a simple PHP library in C.
- GitHub repo: https://github.com/ammarfaizi2/TeaOpenPTY
- Precompiled Shared Lib: https://github.com/ammarfaizi2/TeaOpenPTY/raw/master/compiled/tea_openpty.so
How to use the TeaOpenPTY library?
File 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";
Run
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$
Share solution ↓
Additional Information:
Link To Answer People are also looking for solutions of the problem: warning: a non-numeric value encountered in
Didn't find the answer?
Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.
Similar questions
Find the answer in similar questions on our website.
Write quick answer
Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.