timeout for fread - php

I am calling TCL script from PHP. I am sending a unique string from TCL process to PHP to make sure that script has ended .
If I don't send that string then my fread in PHP is blocked forever .
// PHP code
<?php
$id = 'done'; //Unique string
$app = 'c:/wamp/www/tcl/bin/tclsh84.exe';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("pipe","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process))
{
for($i=0;$i<2;$i++)
{
$output = '';
$continue = true;
$cTimeout = 0;
echo 'loop ', $i, "\n";
fwrite($pipes[0], "source c:/wamp/www/tcl/bin/helloworld.tcl\n");
echo "waiting for idle\n";
$timeout = time();
do {
$read=array($pipes[1]);
$write=array();
$except=array($pipes[1]);
$ready = stream_select($read, $write, $except, 1, 0);
$dif = time()- $timeout;
if ( $ready && $read )
{
$output .= fread($pipes[1], 2048); // is blocked indefinitely
// if the delimiter id shows up in $output
if ( false!==strpos($output, $id) ) {
// the script is done
$continue = false;
}
}
if($dif > 5) //timeout value not working
{
$continue = false;
}
} while($continue);
echo 'loop ', $i, "$output finished\n";
}
proc_close($process);
}
?>
//TCL code
puts "hello"
If i sends "done" from TCL, then my PHP script ends .
But I don't want to send just done, instead I need to do with the help of a timeout .
i.e I want to wait for a certain period of time for the unique string , else I should exit . But I can't seem to implement the timeout in this case.
Please can anyone guide me .

You'll have to rethink the logic of your program but you can:
Register a function for shutdown (as PHP is about to quit runs that function)
Set the max execution-time-limit
And your script would go like this
// sets the maximum execution time (seconds)
set_time_limit(3);
function shutdown () {
// if the script fails some logic goes here
}
// registers the function to run on shutdown
register_shutdown_function('shutdown');
This should set you on the right direction.
Hope it helps!

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).

output problems with proc_open()

I have a small PHP-written CLI script which works as a front-end to CLI-based calc from Linux. The script gets mathematical expressions from user and passes them to calc. Then when user wants to quit he simply enters stop. In this case the script sends exit to calc. The problem with this script is that it displays output only in the end when user sends stop. But I need to have the output of each user's mathematical expression. The script is below:
<?php
define('BUFSIZ', 1024);
define('EXIT_CMD', 'stop');
function printOutput(&$fd) {
while (!feof($fd)) {
echo fgets($fd, BUFSIZ);
}
}
function &getDescriptorSpec()
{
$spec = array(
0 => array("pty"), // stdin
1 => array("pty"), // stdout
2 => array("pty") // stderr
);
return $spec;
}
function readInputLine(&$fd)
{
echo "Enter your input\n";
$line = trim(fgets($fd));
return $line;
}
function sendCmd(&$fd, $cmd)
{
fwrite($fd, "${cmd}\n");
}
function main() {
$spec = getDescriptorSpec();
$process = proc_open("calc", $spec, $pipes);
if (is_resource($process)) {
$procstdin = &$pipes[0];
$procstdout = &$pipes[1];
$fp = fopen('php://stdin', 'r');
while (TRUE) {
$line = readInputLine($fp);
if (0 === strcmp($line, EXIT_CMD)) {
break;
}
sendCmd($procstdin, $line);
}
sendCmd($procstdin, "exit");
fclose($procstdin);
printOutput($procstdout);
fclose($procstdout);
$retval = proc_close($process);
echo "retval = $retval\n";
fclose($fp);
}
}
main();
When using the CLI version of PHP, the output is still buffered - so the usual time that a page is sent to the user is at the end of the script.
As with any version of PHP - using flush() will force the output to be sent to the user.
Also - you should use PHP_EOL, it outputs the correct new lines for whatever platform your on (linux and Windows use different chars - \r\n or \n). PHP_EOL is a safe way of creating a new line.

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');

How to check whether stream has any data?

This what I'm trying to do:
$output = '';
$stream = popen("some-long-running-command 2>&1", 'r');
while (!feof($stream)) {
$meta = stream_get_meta_data($stream);
if ($meta['unread_bytes'] > 0) {
$line = fgets($stream);
$output .= $line;
}
echo ".";
}
$code = pclose($stream);
Looks like this code is not correct, since it gets stuck at the call to stream_get_meta_data(). What is the right way to check whether the stream has some data to read? The whole point here is to avoid locking at fgets().
The correct way to do this is with stream_select():
$stream = popen("some-long-running-command 2>&1", 'r');
while (!feof($stream)) {
$r = array($stream);
$w = $e = NULL;
if (stream_select($r, $w, $e, 1)) {
// there is data to be read
}
}
$code = pclose($stream);
One thing to note though (I'm not sure about this) is that it may be the feof() check that is "blocking" - it may be that the loop never ends because the child process does not close its STDOUT descriptor.

Executing program in php - display and return output

In php there are several methods to execute a shell command:
system()
passthru()
shell_exec()
exec()
First two displays output but doesn't return it.
Last two returns output but doesn't display it.
I want to run shell command which require a lot of time but it displays some output so I know it doesn't hang. However at the end I want to process this output in php. If I choose one of first two I won't get output so I will be unable to process it in php. If I run one of the last two I will be able to process output however my program will hang very long time without outputting anything.
Is there a way to run a shell command which will display output immediately and return it?
Maybe this one will interest you? proc_open() - http://www.php.net/manual/en/function.proc-open.php
And here is a handy snippet which might work for you (it's copied from the comments on the site I gave you the link to):
<?php
/*
* Execute and display the output in real time (stdout + stderr).
*
* Please note this snippet is prepended with an appropriate shebang for the
* CLI. You can re-use only the function.
*
* Usage example:
* chmod u+x proc_open.php
* ./proc_open.php "ping -c 5 google.fr"; echo RetVal=$?
*/
define(BUF_SIZ, 1024); # max buffer size
define(FD_WRITE, 0); # stdin
define(FD_READ, 1); # stdout
define(FD_ERR, 2); # stderr
/*
* Wrapper for proc_*() functions.
* The first parameter $cmd is the command line to execute.
* Return the exit code of the process.
*/
function proc_exec($cmd)
{
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
$ptr = proc_open($cmd, $descriptorspec, $pipes, NULL, $_ENV);
if (!is_resource($ptr))
return false;
while (($buffer = fgets($pipes[FD_READ], BUF_SIZ)) != NULL
|| ($errbuf = fgets($pipes[FD_ERR], BUF_SIZ)) != NULL) {
if (!isset($flag)) {
$pstatus = proc_get_status($ptr);
$first_exitcode = $pstatus["exitcode"];
$flag = true;
}
if (strlen($buffer))
echo $buffer;
if (strlen($errbuf))
echo "ERR: " . $errbuf;
}
foreach ($pipes as $pipe)
fclose($pipe);
/* Get the expected *exit* code to return the value */
$pstatus = proc_get_status($ptr);
if (!strlen($pstatus["exitcode"]) || $pstatus["running"]) {
/* we can trust the retval of proc_close() */
if ($pstatus["running"])
proc_terminate($ptr);
$ret = proc_close($ptr);
} else {
if ((($first_exitcode + 256) % 256) == 255
&& (($pstatus["exitcode"] + 256) % 256) != 255)
$ret = $pstatus["exitcode"];
elseif (!strlen($first_exitcode))
$ret = $pstatus["exitcode"];
elseif ((($first_exitcode + 256) % 256) != 255)
$ret = $first_exitcode;
else
$ret = 0; /* we "deduce" an EXIT_SUCCESS ;) */
proc_close($ptr);
}
return ($ret + 256) % 256;
}
/* __init__ */
if (isset($argv) && count($argv) > 1 && !empty($argv[1])) {
if (($ret = proc_exec($argv[1])) === false)
die("Error: not enough FD or out of memory.\n");
elseif ($ret == 127)
die("Command not found (returned by sh).\n");
else
exit($ret);
}
?>
Maybe you can use popen(), that executes a program and reads its output through a file-handle, like so:
$handle = popen('/bin/ls', 'r');
while ($line = fread($handle, 100)){
echo $line;
}
pclose($handle);

Categories