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?
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 a command line program which requires for the user to type a password in order to run. I want to run it and enter the password through a php script.
I have tried using proc_open, using the following code, but it does not work. It reads the input, but fwrite does not seem to do anything.
$cmd = "cmd /c command_line_program";
$descriptorspec = array(
0 => array('pipe', 'r'), //STDIN
1 => array('pipe', 'w'), //STDOUT
2 => array('pipe', 'r'), //STDERR
);
$process = proc_open(
$cmd,
$descriptorspec, $pipes, null, null);
if (is_resource($process)) {
$buffer = "";
// Read from the command's STDOUT until it's closed.
while (!feof($pipes[1])) {
$input = fread($pipes[1], 8192);
$buffer .= $input;
if (strpos($buffer, 'Password:') !== false){
fwrite($pipes[0], "userpass\n");
}
}
proc_close($process);
} else {
echo 'Not a resource';
}
What am I doing wrong? Is there some other solution except proc_open? I am using windows and php 7.2.
According to PHP proc-open.php documentation and the comments bellow are recommendation to:
close all three streams (store the return value issue):
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
Assign the return value to a variable ( passing passwords to the process)
$exit_status = proc_close($process);
Both are missing in your code.
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);
}
I have a program that reads a JSON request from stdin, which I want to call in PHP.
Here's what I have right now
<?php
echo exec(
'echo \''.json_encode($_POST,JSON_NUMERIC_CHECK).'\' | '.
'program'
);
?>
This works, but is there a more direct way to put a string in PHP in stdin?
Something along the lines of pipe(json_encode($_POST,JSON_NUMERIC_CHECK),'program') maybe?
What I have may be fine in this particular case, but what if instead of JSON, I'll need to pipe binary data? What if it's too long to fit into a shell argument, or contains single quotes?
Edit:
Following miken32's suggestion, I used proc_open() like this:
$proc = proc_open(
'LD_LIBRARY_PATH=/foo/bar/lib program args',
array(0 => array('pipe','r'), 1 => array('pipe','w')),
$pipes,
NULL
//, array('LD_LIBRARY_PATH','/foo/bar/lib')
);
if (is_resource($proc)) {
fwrite($pipes[0],json_encode($_POST,JSON_NUMERIC_CHECK));
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($proc);
}
?>
But I had to add LD_LIBRARY_PATH=/foo/bar/lib to the command argument. The $env argument seems to have no effect. Does anyone know why?
You can use popen() to create a pipeline to a process:
<?php
$data = json_encode($_POST, JSON_NUMERIC_CHECK);
$p = popen("program", "w");
fwrite($p, $data . PHP_EOL);
$exit_code = pclose($p);
If you need to get data back from the program, things get more involved and you need to use proc_open() instead.
<?php
$data = json_encode($_POST, JSON_NUMERIC_CHECK);
$fds = [
0=>["pipe", "r"], // STDIN
1=>["pipe", "w"], // STDOUT
2=>["pipe", "w"], // STDERR
];
$dir = "/path/to/working/directory";
$env = [
"PATH" => "/usr/local/foo/bin:/usr/local/bin:/usr/bin",
"LD_LIBRARY_PATH" => "/usr/local/foo/lib/",
];
$p = proc_open("program", $fds, $pipes, $dir, $env);
fwrite($pipes[0], $data . PHP_EOL);
fclose($pipes[0]);
$return = stream_get_contents($pipes[1]);
$err = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit_code = proc_close($p);
if ($exit_code == 0) {
// successful return
echo $return;
} else {
// error
echo "ERR: $err";
}
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.