I have the following code:
/**
* Executes a program and waits for it to finish, taking pipes into account.
* #param string $cmd Command line to execute, including any arguments.
* #param string $input Data for standard input.
* #param integer $timeout How much to wait from program in msecs (-1 to wait indefinitely).
* #return array Array of "stdout", "stderr" and "return".
*/
function execute($cmd,$stdin=null,$timeout=-1){
$proc=proc_open(
$cmd,
array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
$pipes=null
);
fwrite($pipes[0],$stdin); fclose($pipes[0]);
$stdout=stream_get_contents($pipes[1]); fclose($pipes[1]);
$stderr=stream_get_contents($pipes[2]); fclose($pipes[2]);
$return=proc_close($proc);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
It has two "problems".
The code is synchronous; it freezes until the target process closes.
So far, I've not been able to it from "freezing" without issuing a different kind of command (such as $cmd > /dev/null & on linux and start /B $cmd on windows)
I don't mind the "freeze", at all. I just need to implement that timeout.
Note: It is important that the solution is cross-platform compatible. It is also important that the $cmd doesn't have to change - I'm running some complex commands and I'm afraid there may be some issues, however, this depends on the type of fix - I'm happy to hear these out, just that I'd prefer a different alternative.
I've found some resources that may help:
Run perl file from PHP script but not wait for output on Windows Server
PHP set timeout for script with system call, set_time_limit not working
http://www.shapeshifter.se/2008/08/04/asynchronous-background-execution-with-php/
There is a few mistakes on the code.
That is actually working:
function execute($cmd, $stdin = null, $timeout = -1)
{
$proc=proc_open(
$cmd,
array(array('pipe','r'), array('pipe','w'), array('pipe','w')),
$pipes
);
var_dump($pipes);
if (isset($stdin))
{
fwrite($pipes[0],$stdin);
}
fclose($pipes[0]);
stream_set_timeout($pipes[1], 0);
stream_set_timeout($pipes[2], 0);
$stdout = '';
$start = microtime();
while ($data = fread($pipes[1], 4096))
{
$meta = stream_get_meta_data($pipes[1]);
if (microtime()-$start>$timeout) break;
if ($meta['timed_out']) continue;
$stdout .= $data;
}
$stdout .= stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
$return = proc_close($proc);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
Rather than stream_get_contents, you could look at using fread to gain more finely grained control over what your code is doing. That combined with stream_set_timeout may give you what you're looking for.
I tossed something together as a demonstration of what I was thinking might work - this code is completely untested and comes with no guarantees, but might send you in the right direction. ;)
function execute($cmd,$stdin=null,$timeout=-1){
$proc=proc_open(
$cmd,
array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
$pipes=null
);
fwrite($pipes[0],$stdin); fclose($pipes[0]);
stream_set_timeout($pipes[1], 0);
stream_set_timeout($pipes[2], 0);
$stdout = '';
$start = microtime();
while ($data = fread($pipes[1], 4096))
{
$meta = stream_get_meta_data($pipes[1]);
if (microtime()-$start>$timeout) break;
if ($meta['timed_out']) continue;
$stdout .= $data;
}
$return = proc_close($proc);
$stdout .= stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
This seems to be working for me:
public function toPDF() {
$doc = $this->getDocument();
$descriptor = [
['pipe','r'],
['pipe','w'],
['file','/dev/null','w'], // STDERR
];
$proc = proc_open('/usr/local/project/scripts/dompdf_cli.php',$descriptor,$pipes,sys_get_temp_dir());
fwrite($pipes[0],"$doc[paper]\n$doc[html]");
fclose($pipes[0]);
$timeout = 30;
stream_set_blocking($pipes[1], false);
$pdf = '';
$now = microtime(true);
try {
do {
$elapsed = microtime(true) - $now;
if($elapsed > $timeout) {
throw new \Exception("PDF generation timed out after $timeout seconds");
}
$data = fread($pipes[1], 4096);
if($data === false) {
throw new \Exception("Read failed");
}
if(strlen($data) === 0) {
usleep(50);
continue;
}
$pdf .= $data;
} while(!feof($pipes[1]));
fclose($pipes[1]);
$ret = proc_close($proc);
} catch(\Exception $ex) {
fclose($pipes[1]);
proc_terminate($proc); // proc_close tends to hang if the process is timing out
throw $ex;
}
if($ret !== 0) {
throw new \Exception("dompdf_cli returned non-zero exit status: $ret");
}
// dump('returning pdf');
return $pdf;
}
I'm not sure what the purpose of stream_set_timeout is -- that just sets the per-read timeout, but if you want to limit the overall time, you just have to set the stream to non-blocking mode and then time how long it takes.
Related
I am having issues with my php code. I am executing c++rom fffff code using proc_open() in php.
//from another function I'm calling this function with parameters execute("code.exe", $STDIN, 0.02, 268435456 * 2);
//my c++ code is already executed to .exe file.
function execute($cmd, $stdin, $timeout, $memoryout)
{
$descriptorspec = [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']];
$timer = microtime(true);
$TL = false;
$ML = 0;
$stdout = '';
$process = proc_open($cmd, $descriptorspec, $pipes);
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
while(!feof($pipes[1]))
{
if($timer + $timeout < microtime(true))
{
$TL = true;
break;
}
$ML = max($ML, memory_get_usage());
$stdout .= fread($pipes[1], 1);// . "<br/>";
if($timer + $timeout < microtime(true))
{
$TL = true;
break;
}
}
$status = proc_get_status($process);
if(!$status['running'])
if($status['exitcode'] !== 0) $stdout = 'code:RE';
if($TL) $stdout = 'code:TL';
if($ML > $memoryout) $stdout = 'code:ML';
$pid = proc_get_status($process)['pid'];
strstr(PHP_OS, 'WIN') ? exec("taskkill /F /T /PID $pid") : exec("kill -9 $pid");
fclose($pipes[1]);
proc_close($process);
return $stdout;
}
So, when my c++ code is correctly written, there is no problem, but when i've written something like "while(1);" in my c++ code, my php code does not terminate. while(!feof($pipes[1])) cycle loops forever and I don't know what to do.
I've tried to use getrusage() instead of time stamp but it was worse.
i have tried many time by using flush() to make the script work synchronously, the script prints only data of the first command "gcloud compute ssh yellow" and "ls -la", I am looking to make the script prints the output on every executed fputs().
<?php
$descr = array( 0 => array('pipe','r',),1 => array('pipe','w',),2 => array('pipe','w',),);
$pipes = array();
$process = proc_open("gcloud compute ssh yellow", $descr, $pipes);
if (is_resource($process)) {
sleep(2);
$commands = ["ls -la", "cd /home", "ls", "sudo ifconfig", "ls -l"];
foreach ($commands as $command) {
fputs($pipes[0], $command . " \n");
while ($f = fgets($pipes[1])) {
echo $f;
}
}
fclose($pipes[0]);
fclose($pipes[1]);
while ($f = fgets($pipes[2])) {
echo "\n\n## ==>> ";
echo $f;
}
fclose($pipes[2]);
proc_close($process);
}
Thanks in advance
I believe the problem is the loop you have waiting for input. fgets will only return false if it encounters EOF. Otherwise it returns the line that it read; because the linefeed is included, it doesn't return anything that can be typecast to false. You can use stream_get_line() instead, which does not return the EOL character. Note this would still require your command to return an empty line after its output so it can evaluate to false and break the while loop.
<?php
$prog = "gcloud compute ssh yellow";
$commands = ["ls -la", "cd /home", "ls", "sudo ifconfig", "ls -l"];
$descr = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 =>['pipe','w']];
$pipes = [];
$process = proc_open($prog, $descr, $pipes);
if (is_resource($process)) {
sleep(2);
foreach ($commands as $command) {
fputs($pipes[0], $command . PHP_EOL);
while ($f = stream_get_line($pipes[1], 256)) {
echo $f . PHP_EOL;
}
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
Another option would be to gather the output outside the loop, although this would require you to parse the output if you need to know what output came from what command.
<?php
$prog = "gcloud compute ssh yellow";
$commands = ["ls -la", "cd /home", "ls", "sudo ifconfig", "ls -l"];
$descr = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 =>['pipe','w']];
$pipes = [];
$process = proc_open($prog, $descr, $pipes);
if (is_resource($process)) {
sleep(2);
foreach ($commands as $command) {
fputs($pipes[0], $command . PHP_EOL);
}
fclose($pipes[0]);
$return = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
sorry for stupid question, but
Im trying to set the timeout for program called cirsym. Because sometimes Im facing with infinity process and I have to cancel the operation using ssh...
Would be really thankfull if someone help...
<?
/**
* Execute a command and return it's output. Either wait until the command exits or the timeout has expired.
*
* #param string $cmd Command to execute.
* #param number $timeout Timeout in seconds.
* #return string Output of the command.
* #throws \Exception
*/
function exec_timeout($cmd, $timeout) {
// File descriptors passed to the process.
$descriptors = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
// Start the process.
$process = proc_open('exec ' . $cmd, $descriptors, $pipes);
if (!is_resource($process)) {
throw new \Exception('Could not execute process');
}
// Set the stdout stream to none-blocking.
stream_set_blocking($pipes[1], 0);
// Turn the timeout into microseconds.
$timeout = $timeout * 1000000;
// Output buffer.
$buffer = '';
// While we have time to wait.
while ($timeout > 0) {
$start = microtime(true);
// Wait until we have output or the timer expired.
$read = array($pipes[1]);
$other = array();
stream_select($read, $other, $other, 0, $timeout);
// Get the status of the process.
// Do this before we read from the stream,
// this way we can't lose the last bit of output if the process dies between these functions.
$status = proc_get_status($process);
// Read the contents from the buffer.
// This function will always return immediately as the stream is none-blocking.
$buffer .= stream_get_contents($pipes[1]);
if (!$status['running']) {
break;
}
// Subtract the number of microseconds that we waited.
$timeout -= (microtime(true) - $start) * 1000000;
}
// Check if there were any errors.
$errors = stream_get_contents($pipes[2]);
if (!empty($errors)) {
throw new \Exception($errors);
}
// Kill the process in case the timeout expired and it's still running.
// If the process already exited this won't do anything.
proc_terminate($process, 9);
// Close all streams.
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $buffer;
}
This is the function
private function Exec(){
if(shell_exec($this->canonical_path."cirsym_v2.1.bin") != NULL)
{
return true;
}else{
return false;
}
}
I have written a simple chat program using twisted library in Python. Basically I have a server program(server.py) and a chat program ( client.py)
client.py is a simple python script which would connect to the server on a particular port and print the messages on the terminal.
I can run the server.py and the client.py on a local system and I can chat on different terminals.
I would like to integrate the client.py in PHP and be able to chat through the browser.
I am calling python script through exec in PHP. However it is not working.
exec("python client.py")
Any idea, if I am missing anything ?
Not sure if I can help you, but here's some things I'd consider:
Have you tried executing a different command, does that work?
Have you set the permissions of the .php file and Python.exe correctly?
Can you run your command from a terminal window (either Windows/Mac/Linux) from the folder your PHP file is in?
If you've already tried all of these things, I can't think of another solution.. Good luck!
You might find the following program helpful as a starting point. It is designed to run a Python program:
<?php
// Check that test is not FALSE; otherwise, show user an error.
function assert_($test)
{
if ($test === FALSE)
{
echo '<html><head><title>Proxy</title></head><body><h1>Fatal Error</h1></body></html>';
exit(1);
}
}
// Patch this version of PHP with curl_setopt_array as needed.
if (!function_exists('curl_setopt_array')) {
function curl_setopt_array($ch, $curl_options)
{
foreach ($curl_options as $option => $value) {
if (!curl_setopt($ch, $option, $value)) {
return FALSE;
}
}
return TRUE;
}
}
// Fetch the URL by logging into proxy with credentials.
function fetch($url, $proxy, $port, $user, $pwd)
{
$ch = curl_init($url);
assert_($ch);
$options = array(
CURLOPT_PROXY => $proxy . ':' . $port,
CURLOPT_PROXYAUTH => CURLAUTH_NTLM,
CURLOPT_PROXYUSERPWD => $user . ':' . $pwd,
CURLOPT_RETURNTRANSFER => TRUE
);
assert_(curl_setopt_array($ch, $options));
$transfer = curl_exec($ch);
curl_close($ch);
assert_($transfer);
return $transfer;
}
// Run path with stdin and return program's status code.
function order($path, $stdin, &$stdout, &$stderr)
{
$cmd = './' . basename($path);
$descriptorspec = array(
array('pipe', 'r'),
array('pipe', 'w'),
array('pipe', 'w')
);
$cwd = dirname($path);
$process = proc_open($cmd, $descriptorspec, $pipes, $cwd, $_REQUEST);
assert_($process);
for ($total = 0; $total < strlen($stdin); $total += $written)
{
$written = fwrite($pipes[0], substr($stdin, $total));
assert_($written);
}
assert_(fclose($pipes[0]));
$stdout = stream_get_contents($pipes[1]);
assert_($stdout);
assert_(fclose($pipes[1]));
$stderr = stream_get_contents($pipes[2]);
assert_($stderr);
assert_(fclose($pipes[2]));
return proc_close($process);
}
// Collect information to run over the proxy.
# $user = $_REQUEST['user'];
# $pwd = $_REQUEST['pwd'];
// Fetch the URL and process it with the backend.
$transfer = fetch('http://rssblog.whatisrss.com/feed/', 'labproxy.pcci.edu', 8080, $user, $pwd);
$status = order('/home/Chappell_Stephen/public_html/backend.py', $transfer, $stdout, $stderr);
// Check for errors and display the final output.
assert_(strlen($stderr) == 0);
echo $stdout;
?>
I am executing a TCL script from PHP using proc_open.
I first open the TCL shell
2) Send a command using fwrite
3) What I need is fread to wait/block until the
command sent by fwrite is complete
and get all the contents .The command may take some time to complete.
(I am able to read just 2 lines and then it is going off to the next loop)
Can someone guide me.
The present code is
<?php
$app = 'tclsh84';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/wamp/www/tcl/bin/g.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process))
{
for($i=0;$i<4;$i++)
{
fwrite($pipes[0], 'source c:/wamp/www/tcl/bin/test.tcl'."\n");
$content= fread($pipes[1],8192)
print "$content";
}
fclose($pipes[0]);
fclose($pipes[1]);
proc_close($process);
}
?>
I'm thinking about a combination of
stream_select and/or feof()
fread() and concatenation of the partial results ($result .= fread())
and maybe proc_get_status() to determine the end of the process
You want to wait until the tcl application doesn't write something to its stdout for a certain amount of time (presuming that this means the end of the last command) and then send the next command/line to its stdin?
edit:
Seems like you can send all commands to the tcl shell at once and they are processed one by one, i.e. the shell reads the next input line/command when it's done with the previous one. I've tested this with the script.
incr a 1
after 1000
puts [concat [clock seconds] $a]
and
<?php
$app = 'c:/programme/tcl/bin/tclsh85.exe';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/god.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "set a 1\n");
for($i=0;$i<4;$i++) {
fwrite($pipes[0], "source c:/helloworld.tcl\n");
}
// when all scripts are done the shell shall exit
fwrite($pipes[0], "exit\n");
fclose($pipes[0]);
do {
$read=array($pipes[1]); $write=array(); $except=array($pipes[1]);
// wait up to 1 second for new output of the tcl process
$ready = stream_select($read, $write, $except, 1, 0);
if ( $ready && $read /* is not empty */) {
// get the partial output
$r = fread($pipes[1], 2048);
echo $r;
}
// is the process still running?
$status = proc_get_status($process);
} while($status['running']);
fclose($pipes[1]);
proc_close($process);
}
?>
You probably want to add some more error handling. E.g. if stream_select() returns x times with an timeout something might have gone wrong.
edit2:
Let the shell print something you can scan for after each script.
<?php
// something that's not in the "normal" output of the scripts
$id = 'done'. time();
$app = 'c:/programme/tcl/bin/tclsh85.exe';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/god.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "set a 1\n");
for($i=0;$i<4;$i++) {
$output = '';
$continue = true;
$cTimeout = 0;
echo 'loop ', $i, "\n";
fwrite($pipes[0], "source c:/helloworld.tcl\n");
fwrite($pipes[0], "puts $id\n");
echo "waiting for idle\n";
do {
$read=array($pipes[1]);
$write=array();
$except=array($pipes[1]);
$ready = stream_select($read, $write, $except, 1, 0);
if ( $ready && $read ) {
$output .= fread($pipes[1], 2048);
// if the delimiter id shows up in $output
if ( false!==strpos($output, $id) ) {
// the script is done
$continue = false;
}
}
} while($continue);
echo 'loop ', $i, " finished\n";
}
proc_close($process);
}
?>
Try:
$content = '';
while(!feof($pipes[1]))
{
$content .= fread($pipes[1],8192);
}
Does that wait?