PHP ssh2_exec no feof on empty stdout - php

This PHP snippet should execute a simple command via SSH (stripped down to minimal working example):
$sshconn = ssh2_connect($HostAddr, 22);
ssh2_auth_pubkey_file($sshconn, $user, $sshkey . '.pub', $sshkey);
$stdout = ssh2_exec($sshconn, 'echo hello');
if ($stdout !== false)
{
stream_set_blocking($stdout, true);
while (!feof($stdout))
{
$proc_stdout = fgets($stdout, 3E6);
if ($proc_stdout !== false) echo $proc_stdout;
}
fclose($stdout);
}
Works great as long as there is any output to stdout. However, if stdout remains empty, the loop turns into an endless loop.
$stdout = ssh2_exec($sshconn, 'echo hello >&2');
How do I read the stdout properly if
stdout may be empty
but stdout may also very large (several Gigabytes, impossible to slurp into a variable by a single call to stream_get_contents or the like).
Addendum: My real world code that hangs executes mysqldump with an unknown parameter. Obviously, echo -n >&2 works as expected, although its stdout is also empty.

If anyone stumbles upon this too: stderr has to be read too.
$sshconn = ssh2_connect($HostAddr, 22);
ssh2_auth_pubkey_file($sshconn, $user, $sshkey . '.pub', $sshkey);
$stdout = ssh2_exec($sshconn, 'echo Hello >&2');
if ($stdout !== false)
{
$stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR);
stream_set_blocking($stdout, false);
stream_set_blocking($stderr, false);
while (!feof($stdout))
{
$proc_stdout = fgets($stdout, 3E6);
if ($proc_stdout !== false) echo $proc_stdout;
$proc_stderr = fgets($stderr, 3E6);
if ($proc_stderr !== false) fwrite(STDERR, $stderr);
}
fclose($stdout); fclose($stderr);
}
The disadvantage of this is that the SSH connection is no longer usable afterwards (a further ssh2_exec cannot be executed).
A possible solution is to keep both streams blocking and read stdout and stderr non-interleaved:
$stdout = ssh2_exec($sshconn, 'echo Hello >&2');
$stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR);
stream_set_blocking($stdout, true);
stream_set_blocking($stderr, true);
while ($content = fread($stdout, 3E6)) echo $content;
while ($content = fread($stderr, 3E6)) fwrite(STDERR, $content);
I invite anyone who knows how to have the streams interleaved and keep the SSH connection usable to post another answer.

Related

How to get terminal text from gnu screen session?

How do i get the terminal text from a GNU Screen session in PHP? I am puzzled because GitHub CoPilot suggest i can do
function getTerminalTextFromGnuScreen(string $screenName): string
{
$screenText = shell_exec("screen -S " . escapeshellarg($screenName) . " -X hardcopy -h");
return $screenText;
}
but that does not actually get me anything, just emptystring.
I came up with a much more complex, convoluted, and dodgy implementation, which works, but i hope, as CoPilot suggest, that there is a much easier way to do this, hence asking here: how should it be done? my dodgy implementation:
function getTerminalTextFromGnuScreenDodgy(string $screenName, bool $skipTerminalSizeControlCharacters = true): string
{
// the double escapeshellarg is intentional.
$cmd = 'script --command ' . escapeshellarg('screen -x -r ' . escapeshellarg($screenName)) . ' /dev/null ';
$proc = proc_open($cmd, [['pipe', 'rb'], ['pipe', 'wb'], ['pipe', 'wb']], $pipes);
stream_set_blocking($pipes[1], true);
fgets($pipes[1]); // skip "Script started, output log file is '/dev/null'." line
$ret = '';
if ($skipTerminalSizeControlCharacters) {
$ret = fgets($pipes[1]); // contains terminal size characters
$lastControlCharacter = strrpos($ret, "\x1b"); // THIS IS NOT 100% RELIABLE, and i don't know how to make it reliable either :(
$ret = substr($ret, $lastControlCharacter + strlen("\x1b[2J")); // skip terminal size characters
}
stream_set_blocking($pipes[1], false);
for (;;) {
$r = [$pipes[1]];
$w = $e = null;
$sel = stream_select($r, $w, $e, 0, 10000);
$tmp = stream_get_contents($pipes[1]);
if ($tmp === false || $tmp === '') {
break;
}
$ret .= $tmp;
}
fclose($pipes[1]);
// send ctrl+AD... why is \x01 equivalent to ctrl+AD? i have no idea! but it seems to work
fwrite($pipes[0], "\x01");
fclose($pipes[0]);
fclose($pipes[2]); // contains a "script: write error" message.. no idea why.
proc_close($proc);
return $ret;
}
so much wasted effort.. the answer is indeed -X hardcopy, but the the code refuse to writes to both stdout and /dev/stdout, and if you tell it to write to the filename - which traditionally means stdout, it will write to ./-, and if no file is given it default to the filename "hardcopy.n" -
Why? I have no idea, but regardless, use tmpfile() to get a output file for -X hardcopy:
function getTerminalTextFromGnuScreen(string $screenName)
{
$tmph = tmpfile();
$tmpf = stream_get_meta_data($tmph)['uri'];
shell_exec("screen -S " . escapeshellarg($screenName) . " -X hardcopy " . escapeshellarg($tmpf));
$screenText = file_get_contents($tmpf);
fclose($tmph);
return $screenText;
}
don't know why CoPilot added the -h argument, but the documentation over at https://www.gnu.org/software/screen/manual/screen.html says
‘-h num’
Set the history scrollback buffer to be num lines high. Equivalent to the defscrollback command (see Copy).

Impose time limit to popen/fgets in PHP

I want impose a time limit to a process reading using fgets opened by popen in PHP.
I have the next code:
$handle = popen("tail -F -n 30 /tmp/pushlog.txt 2>&1", "r");
while(!feof($handle)) {
$buffer = fgets($handle);
echo "data: ".$buffer."\n";
#ob_flush();
flush();
}
pclose($handle);
I tried without success:
set_time_limit(60);
ignore_user_abort(false);
The process is as follow:
The browser send a GET request waiting for a Answer in HTML5 Server side
events format.
The request is received by AWS Load Balancer and is
forwarded to EC2 instances.
The answer is the last 30 lines of the file
The browser receive it in 30 messages and the connection is persisted.
If tail command sends a new line it is returned else fgets wait undefined time until new line is returned from tail command.
AWS Load Balancer after 60 seconds of network inactivity (No new lines in 60 seconds) closes the connection to the browser. The connection to EC2 instance is not closed.
The browser detect that the connection is closed and it opens a new connection, the process go back to step 1.
AS this steps describe, the connection between AWS Load Balancer and EC2 instance is never closed, after a few hours/days there is hundreds and hundreds of tail and httpd process running and the server start not answering.
Of course it appear to be a AWS Load Balancer bug, but I don't want start a process to gain the attention from Amazon and wait for a fix.
My temporary solution is do a sudo kill tail to kill the process before the server becomes unstable.
I think PHP doesn't stop the script because PHP is "blocked" waiting for fgets to finish.
I know that the time limit of AWS Load Balancer is editable, but I want keep in the default value, even a higher limit is not going to fix the problem.
I don't know if I need change the question to How to execute a process in linux with a time limit / timeout?.
PHP 5.5.22 / Apache 2.4 / Linux Kernel 3.14.35-28.38.amzn1.x86_64
Tested with PHP 5.5.20:
//Change configuration.
set_time_limit(0);
ignore_user_abort(true);
//Open pipe & set non-blocking mode.
$descriptors = array(0 => array('file', '/dev/null', 'r'),
1 => array('pipe', 'w'),
2 => array('file', '/dev/null', 'w'));
$process = proc_open('exec tail -F -n 30 /tmp/pushlog.txt 2>&1',
$descriptors, $pipes, NULL, NULL) or exit;
$stream = $pipes[1];
stream_set_blocking($stream, 0);
//Call stream_select with a 10 second timeout.
$read = array($stream); $write = NULL; $except = NULL;
while (!feof($stream) && !connection_aborted()
&& stream_select($read, $write, $except, 10)) {
//Print out all the lines we can.
while (($buffer = fgets($stream)) !== FALSE) {
echo 'data: ' . $buffer . "\n";
#ob_flush();
flush();
}
}
//Clean up.
fclose($stream);
$status = proc_get_status($process);
if ($status !== FALSE && $status['running'] === TRUE)
proc_terminate($process);
proc_close($process);
Rather than using a process file pointer, I went with my "multitasking" approach. I use this code to spawn other "processes" Kind of a multitasking cheat.
I call a Script, hang.php, that just hangs for 90 seconds: sleep(90).
You may want to adjust the stream and stream_select timeouts.
Create stream(s)
header('Content-Type: text/plain; charset=utf-8');
$timeout = 20;
$result = array();
$sockets = array();
$buffer_size = 8192;
$id = 0;
$stream = stream_socket_client("ispeedlink.com:80", $errno,$errstr, $timeout,
STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT);
if ($stream) {
$sockets[$id++] = $stream; // supports multiple sockets
$http = "GET /testbed/hang.php HTTP/1.0\r\nHost: ispeedlink.com\r\n\r\n";
fwrite($stream, $http);
}
else {
echo "$id Failed\n";
}
Additional scripts can be run by adding the stream: $sockets[$id++] = $stream;
Below will put anything read in to the $result[$id] array.
Monitor the streams:
while (count($sockets)) {
$read = $sockets;
stream_select($read, $write = NULL, $except = NULL, $timeout);
if (count($read)) {
foreach ($read as $r) {
$id = array_search($r, $sockets);
$data = fread($r, $buffer_size);
if (strlen($data) == 0) { // either reads data or EOF
echo "$id Closed: " . date('h:i:s') . "\n\n\n";
fclose($r);
unset($sockets[$id]);
}
else {
$result[$id] .= $data;
}
}
}
else {
echo 'Timeout: ' . date('h:i:s') . "\n\n\n";
break;
}
}
echo system('ps auxww');
.
When I want to kill a process I use system('ps auxww') to get the pid and kill it with system("kill $pid")
kill.php
header('Content-Type: text/plain; charset=utf-8');
//system('kill 220613');
echo system('ps auxww');

PHP: Need to close STDIN in order to read STDOUT?

I recently tried to communicate with a binary on my Ubuntu webserver [1] using the PHP function proc_open. I can establish a connection and define the pipes STDIN, STDOUT, and STDERR. Nice.
Now the bimary I am talking to is an interactive computer algebra software - therefore I would like to keep both STDOUT and STDIN alive after the first command such that I can still use the application a few lines later in an interactive manner (direct user inputs from a web-frontend).
However, as it turns out, the PHP functions to read the STDOUT of the binary (either stream_get_contents or fgets) need a closed STDIN before they can work. Otherwise the program deadlocks.
This is a severe drawback since I can not just reopen the closed STDIN after closing it. So my question is: why does my script deadlock if I want to read the STDOUT when my STDIN is still alive?
Thanks
Jens
[1] proc_open returns false but does not write in error file - permissions issue?
my source:
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","./error.log","a")
) ;
// define current working directory where files would be stored
$cwd = './' ;
// open reduce
$process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd) ;
if (is_resource($process)) {
// some valid Reduce commands
fwrite($pipes[0], 'load excalc; operator x; x(0) := t; x(1) := r;');
// if the following line is removed, the script deadlocks
fclose($pipes[0]);
echo "output: " . stream_get_contents($pipes[1]);
// close pipes & close process
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
EDIT:
This code kind of works. Kind of because it uses usleeps to wait for the non-blocked STDOUT to be filled with data. How do I do that more elegantly?
# Elias: By polling the $status['running'] entry you can only determine if the overall process is still running, but not if the process is busy or idling... That is why I have to include these usleeps.
define('TIMEOUT_IN_MS', '100');
define('TIMEOUT_STEPS', '100');
function getOutput ($pipes) {
$result = "";
$stage = 0;
$buffer = 0;
do {
$char = fgets($pipes[1], 4096);
if ($char != null) {
$buffer = 0;
$stage = 1;
$result .= $char;
} else if ($stage == "1") {
usleep(TIMEOUT_IN_MS/TIMEOUT_STEPS);
$buffer++;
if ($buffer > TIMEOUT_STEPS) {
$stage++;
}
}
} while ($stage < 2);
return $result;
}
$descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w") ) ;
// define current working directory where files would be stored
$cwd = './' ;
// open reduce
$process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd);
if (is_resource($process)) {
stream_set_blocking($pipes[1], 0);
echo "startup output:<br><pre>" . getOutput($pipes) . "</pre>";
fwrite($pipes[0], 'on output; load excalc; operator x; x(0) := t; x(1) := r;' . PHP_EOL);
echo "output 1:<br><pre>" . getOutput($pipes) . "</pre>";
fwrite($pipes[0], 'coframe o(t) = sqrt(1-2m/r) * d t, o(r) = 1/sqrt(1-2m/r) * d r with metric g = -o(t)*o(t) + o(r)*o(r); displayframe;' . PHP_EOL);
echo "output 2:<br><pre>" . getOutput($pipes) . "</pre>";
// close pipes & close process
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
This reminds me of a script I wrote a while back. While it might serve as inspiration to you (or others), it doesn't do what you need. What it does contain is an example of how you can read the output of a stream, without having to close any of the streams.
Perhaps you can apply the same logic to your situation:
$allInput = array(
'load excalc; operator x; x(0) := t; x(1) := r;'
);//array with strings to pass to proc
if (is_resource($process))
{
$output = '';
$input = array_shift($allInput);
do
{
usleep(200);//make sure the running process is ready
fwrite(
$pipes,
$input.PHP_EOL,//add EOL
strlen($input)+1
);
fflush($pipes[0]);//flush buffered data, write to stream
usleep(200);
$status = proc_get_status($process);
while($out = fread($pipes[1], 1024) && !feof($pipes[1]))
$output .= $out;
} while($status['running'] && $input = array_shift($allInput));
//proc_close & fclose calls here
}
Now, seeing as I don't know what it is exactly you are trying to do, this code will need to be tweaked quite a bit. You may, for example, find yourself having to set the STDIN and STDOUT pipes as non-blocking.
It's a simple matter of adding this, right after calling proc_open, though:
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
Play around, have fun, and perhaps let me know if this answer was helpful in any way...
My guess would be that you're doing everything correctly, except that the binary is never notified that it has received all the input and can start to work. By closing STDIN, you're kicking off the work process, because it's clear that there will be no more input. If you're not closing STDIN, the binary is waiting for more input, while your side is waiting for its output.
You probably need to end your input with a newline or whatever other protocol action is expected of you. Or perhaps closing STDIN is the action that's expected of you. Unless the process is specifically created to stay open and continue to stream input, you can't make it do it. If the process reads all input, processes it, returns output and then quits, there's no way you can make it stay alive to process more input later. If the process explicitly supports that behaviour, there should be a definition on how you need to delimit your input.

Reading from STDIN pipe when using proc_open

I am trying to make a website where people can compile and run their code online, thus we need to find an interactive way for users to send instructions.
Actually, what first comes to mind is exec() or system(), but when users want to input sth, this way won't work. So we have to use proc_open().
For instance, the following code
int main()
{
int a;
printf("please input a integer\n");
scanf("%d", &a);
printf("Hello World %d!\n", a);
return 0;
}
When I used proc_open(), like this
$descriptorspec = array(
0 => array( 'pipe' , 'r' ) ,
1 => array( 'pipe' , 'w' ) ,
2 => array( 'file' , 'errors' , 'w' )
);
$run_string = "cd ".$addr_base."; ./a.out 2>&1";
$process = proc_open($run_string, $descriptorspec, $pipes);
if (is_resource($process)) {
//echo fgets($pipes[1])."<br/>";
fwrite($pipes[0], '12');
fclose($pipes[0]);
while (!feof($pipes[1]))
echo fgets($pipes[1])."<br/>";
fclose($pipes[1]);
proc_close($process);
}
When running the C code, I want to get the first STDOUT stream, and input the number, then get the second STDOUT stream. But if I have the commented line uncommented, the page will be blocked.
Is there a way to solve the problem? How can I read from the pipe while not all data has been put there? Or is there a better way to write this kind of interactive program?
It is more a C or a glibc problem. You'll have to use fflush(stdout).
Why? And what's the difference between running a.out in a terminal and calling it from PHP?
Answer: If you run a.out in a terminal (being stdin a tty) then the glibc will use line buffered IO. But if you run it from another program (PHP in this case) and it's stdin is a pipe (or whatever but not a tty) than the glibc will use internal IO buffering. That's why the first fgets() blocks if uncommented. For more info check this article.
Good news: You can control this buffering using the stdbuf command. Change $run_string to:
$run_string = "cd ".$addr_base.";stdbuf -o0 ./a.out 2>&1";
Here comes a working example. Working even if the C code don't cares about fflush() as it is using the stdbuf command:
Starting subprocess
$cmd = 'stdbuf -o0 ./a.out 2>&1';
// what pipes should be used for STDIN, STDOUT and STDERR of the child
$descriptorspec = array (
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
// open the child
$proc = proc_open (
$cmd, $descriptorspec, $pipes, getcwd()
);
set all streams to non blocking mode
// set all streams to non blockin mode
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking(STDIN, 0);
// check if opening has succeed
if($proc === FALSE){
throw new Exception('Cannot execute child process');
}
get child pid. we need it later
// get PID via get_status call
$status = proc_get_status($proc);
if($status === FALSE) {
throw new Exception (sprintf(
'Failed to obtain status information '
));
}
$pid = $status['pid'];
poll until child terminates
// now, poll for childs termination
while(true) {
// detect if the child has terminated - the php way
$status = proc_get_status($proc);
// check retval
if($status === FALSE) {
throw new Exception ("Failed to obtain status information for $pid");
}
if($status['running'] === FALSE) {
$exitcode = $status['exitcode'];
$pid = -1;
echo "child exited with code: $exitcode\n";
exit($exitcode);
}
// read from childs stdout and stderr
// avoid *forever* blocking through using a time out (50000usec)
foreach(array(1, 2) as $desc) {
// check stdout for data
$read = array($pipes[$desc]);
$write = NULL;
$except = NULL;
$tv = 0;
$utv = 50000;
$n = stream_select($read, $write, $except, $tv, $utv);
if($n > 0) {
do {
$data = fread($pipes[$desc], 8092);
fwrite(STDOUT, $data);
} while (strlen($data) > 0);
}
}
$read = array(STDIN);
$n = stream_select($read, $write, $except, $tv, $utv);
if($n > 0) {
$input = fread(STDIN, 8092);
// inpput to program
fwrite($pipes[0], $input);
}
}
The answer is surprisingly simple: leave $descriptorspec empty. If you do so, the child process will simply use the STDIN/STDOUT/STDERR streams of the parent.
➜ ~ ✗ cat stdout_is_atty.php
<?php
var_dump(stream_isatty(STDOUT));
➜ ~ ✗ php -r 'proc_close(proc_open("php stdout_is_atty.php", [], $pipes));'
/home/chx/stdout_is_atty.php:3:
bool(true)
➜ ~ ✗ php -r 'passthru("php stdout_is_atty.php");'
/home/chx/stdout_is_atty.php:3:
bool(false)
➜ ~ ✗ php -r 'exec("php stdout_is_atty.php", $output); print_r($output);'
Array
(
[0] => /home/chx/stdout_is_atty.php:3:
[1] => bool(false)
)
Credit goes to John Stevenson, one of the maintainers of composer.
If you are interested why this happens: PHP does nothing for empty descriptors and uses the C / OS defaults which just happens to be the desired one.
So the C code responsible for proc_open always merely iterates the descriptors. If there are no descriptors specified then all that code does nothing. After that, the actual execution of the child -- at least on POSIX systems -- happens via calling fork(2) which makes the child inherit file descriptors (see this answer). And then the child calls one of execvp(3) / execle(3) / execl(3) . And as the manual says
The exec() family of functions replaces the current process image with a new process image.
Perhaps it's more understandable to say the memory region containing the parent is replaced by the new program. This is accessible as /proc/$pid/mem, see this answer for more. However, the system keeps a tally of the opened files outside of this region. You can see them in /proc/$pid/fd/ -- and STDIN/STDOUT/STDERR are just shorthands for file descriptors 0/1/2. So when the child replaces the memory, the file descriptors just stay in place.

shell_exec in PHP returns empty string

shell_exec and exec are not returning any content. I can't figure out what's wrong.
Here's some code:
echo 'test: ';
$output = shell_exec('whoami');
var_export($output, TRUE);
echo PHP_EOL . '<br>' . PHP_EOL;
And here's the source of the output
test 2:
<br>
I do not have control over the host, but I believe they're running SuPHP. According to phpinfo, safe_mode is off. Running whoami from SSH outputs the expected value.
I'm at a loss. Any idea how to debug this?
You're never printing the $output variable. The var_export() call returns the content of the variable when you call it with a true second parameter, it does not print it directly.
If you want the output from a shell command read back into PHP, you're probably going to need popen(). For example:
if( ($fp = popen("some shell command", "r")) ) {
while( !feof($fp) ) {
echo fread($fp, 1024);
flush(); // input will be buffered
}
fclose($fp);
}

Categories