output problems with proc_open() - php

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.

Related

Tail -f live output process still running

I am trying to do a live output of a file called fail2ban.log this log is on my linux server and i try to proccess it using. The tail process stay opened so it uses loads of cpu performance after some pepoles open the page since the process stay opened
I tried some solution of killing it with
while(true)
{
if($flag === false) die(); // Or exit if you prefer
}
The server is on Apache2
My code :
<?php
echo "Number of banned ip (live) : ";
$hand = popen("grep 'Ban' /var/log/fail2ban.log | wc -l 2>&1", 'r');
while(!feof($hand)) {
$buff = fgets($hand);
echo "$buff<br/>\n";
ob_flush();
flush();
}
pclose($hand);
echo " ";
echo "Current Log (go at the bottom of the page for the live log)";
echo " ";
$output = shell_exec('cat /var/log/fail2ban.log 2>&1');
echo "<pre>$output</pre>";
echo "Live Logs";
echo "<h1> </h1> ";
echo " ";
$handle = popen("tail -f /var/log/fail2ban.log 2>&1", 'r');
while(!feof($handle)) {
$buffer = fgets($handle);
echo "$buffer<br/>\n";
ob_flush();
flush();
}
pclose($handle);
?>
I want it to kill the process when the user quit the page.
No #jhnc In this case, popen is guilty, which does not end the process when the program is closed.
In general, PHP is one of the worst choices to implement tail -f. It's better to use node + websocket.
In this case, you need to check if something has been added to the file by another method. From http://php.net/manual/en/function.inotify-init.php#101093
<?php
/**
* Tail a file (UNIX only!)
* Watch a file for changes using inotify and return the changed data
*
* #param string $file - filename of the file to be watched
* #param integer $pos - actual position in the file
* #return string
*/
function tail($file,&$pos) {
// get the size of the file
if(!$pos) $pos = filesize($file);
// Open an inotify instance
$fd = inotify_init();
// Watch $file for changes.
$watch_descriptor = inotify_add_watch($fd, $file, IN_ALL_EVENTS);
// Loop forever (breaks are below)
while (true) {
// Read events (inotify_read is blocking!)
$events = inotify_read($fd);
// Loop though the events which occured
foreach ($events as $event=>$evdetails) {
// React on the event type
switch (true) {
// File was modified
case ($evdetails['mask'] & IN_MODIFY):
// Stop watching $file for changes
inotify_rm_watch($fd, $watch_descriptor);
// Close the inotify instance
fclose($fd);
// open the file
$fp = fopen($file,'r');
if (!$fp) return false;
// seek to the last EOF position
fseek($fp,$pos);
// read until EOF
while (!feof($fp)) {
$buf .= fread($fp,8192);
}
// save the new EOF to $pos
$pos = ftell($fp); // (remember: $pos is called by reference)
// close the file pointer
fclose($fp);
// return the new data and leave the function
return $buf;
// be a nice guy and program good code ;-)
break;
// File was moved or deleted
case ($evdetails['mask'] & IN_MOVE):
case ($evdetails['mask'] & IN_MOVE_SELF):
case ($evdetails['mask'] & IN_DELETE):
case ($evdetails['mask'] & IN_DELETE_SELF):
// Stop watching $file for changes
inotify_rm_watch($fd, $watch_descriptor);
// Close the inotify instance
fclose($fd);
// Return a failure
return false;
break;
}
}
}
}
// Use it like that:
$lastpos = 0;
$file = '/var/log/fail2ban.log'l
while (true) {
echo tail($file,$lastpos);
ob_flush();
flush();
}
?>
And you can't forget about max_execution_time and Apache limits

How to check if bash input is being redirect to PHP script?

I'm writing a PHP script and I would like to be able to optionally use a file as the script input. This way:
$ php script.php < file.txt
I'm, actually, able to do that using file_get_contents:
$data = file_get_contents('php://stdin');
However, if I don't pass a file to the input, the scripts hangs indefinetelly, waiting for an input.
I tried the following, but it didn't work:
$data = '';
$in = fopen('php://stdin', 'r');
do {
$bytes = fread($in, 4096);
// Maybe the input will be empty here?! But no, it's not :(
if (empty($bytes)) {
break;
}
$data .= $bytes;
} while (!feof($in));
The script waits for fread to return a value, but it never returns. I guess it waits for some input the same way file_get_contents does.
Another attempt was made by replacing the do { ... } while loop by a while { ... }, checking for the EOF before than trying to read the input. But that also didn't work.
Any ideas on how can I achieve that?
You can set STDIN to be non-blocking via the stream_set_blocking() function.
function stdin()
{
$stdin = '';
$fh = fopen('php://stdin', 'r');
stream_set_blocking($fh, false);
while (($line = fgets($fh)) !== false) {
$stdin .= $line;
}
return $stdin;
}
$stdin = stdin(); // returns the contents of STDIN or empty string if nothing is ready
Obviously, you can change the use of line-at-a-time fgets() to hunk-at-a-time fread() as per your needs.

Is it possible to run two commands and deal with both outputs on same page

I am running two linux commands with popen() in php. If I run 1, I can gather the output and process it fine. If I run two at the same time while funneling to the page with 2>&1, output gets jumbled. Is it possible to run two commands and deal with both outputs on same page?
I basically duplicated the bottom code for each command
$handle = popen ("-some long command 2>&1");
while (false !== ($char = fgetc($handle)))
{
if ($char == "\r")
{
// You could now parse the $line for status information.
$line= "$line\n";
if (preg_match("/Duration: (.*?),/", $line, $matches)){
//do stuff
}
$line = "";
} else {
$line .= $char;
}
ob_flush();
flush();
}
pclose ($handle);
}
proc_open allows multiple, asynchronous commands execution; you would get the output through the stdout pipe.
PS: never duplicate code; use functions instead!

How do I write a command-line interactive PHP script?

I want to write a PHP script that I can use from the command line. I want it to prompt and accept input for a few items, and then spit out some results. I want to do this in PHP, because all my classes and libraries are in PHP, and I just want to make a simple command line interface to a few things.
The prompting and accepting repeated command line inputs is the part that's tripping me up. How do I do this?
The I/O Streams page from the PHP manual describes how you can use STDIN to read a line from the command line:
<?php
$line = trim(fgets(STDIN)); // reads one line from STDIN
fscanf(STDIN, "%d\n", $number); // reads number from STDIN
?>
From PHP: Read from Keyboard – Get User Input from Keyboard Console by Typing:
You need a special file: php://stdin which stands for the standard input.
print "Type your message. Type '.' on a line by itself when you're done.\n";
$fp = fopen('php://stdin', 'r');
$last_line = false;
$message = '';
while (!$last_line) {
$next_line = fgets($fp, 1024); // read the special file to get the user input from keyboard
if (".\n" == $next_line) {
$last_line = true;
} else {
$message .= $next_line;
}
}
I'm not sure how complex your input might be, but readline is an excellent way to handle it for interactive CLI programs.
You get the same creature comforts out of it that you would expect from your shell, such as command history.
Using it is as simple as:
$command = readline("Enter Command: ");
/* Then add the input to the command history */
readline_add_history($command);
If available, it really does make it simple.
Here a typical do-case-while for console implementation:
do {
$cmd = trim(strtolower( readline("\n> Command: ") ));
readline_add_history($cmd);
switch ($cmd) {
case 'hello': print "\n -- HELLO!\n"; break;
case 'bye': break;
default: print "\n -- You say '$cmd'... say 'bye' or 'hello'.\n";
}
} while ($cmd!='bye');
where user can use arrows (up and down) to access the history.
I found an example on PHP.net, Utiliser PHP en ligne de commande:
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
if (trim($line) != 'yes') {
...
Simple:
#!/usr/bin/php
<?php
define('CONFIRMED_NO', 1);
while (1) {
fputs(STDOUT, "\n"."***WARNING***: This action causes permanent data deletion.\nAre you sure you're not going to wine about it later? [y,n]: ");
$response = strtolower(trim(fgets(STDIN)));
if( $response == 'y' ) {
break;
} elseif( $response == 'n' ) {
echo "\n",'So I guess you changed your mind eh?', "\n";
exit (CONFIRMED_NO);
} else {
echo "\n", "Dude, that's not an option you idiot. Let's try this again.", "\n";
continue;
}
}
echo "\n","You're very brave. Let's continue with what we wanted to do.", "\n\n";
My five cents:
Using STDOUT and STDIN:
fwrite(STDOUT, "Please enter your Message (enter 'quit' to leave):\n");
do{
do{
$message = trim(fgets(STDIN));
} while($message == '');
if(strcasecmp($message, 'quit') != 0){
fwrite(STDOUT, "Your message: ".$message."\n");
}
}while(strcasecmp($message,'quit') != 0);
// Exit correctly
exit(0);
The algorithm is simple:
until done:
display prompt
line := read a command line of input
handle line
It's very trivial to use an array that maps commands to callback functions that handle them. The entire challenge is roughly a while loop, and two function calls. PHP also has a readline interface for more advanced shell applications.
One line of code (line 2):
<?php
$name = trim(shell_exec("read -p 'Enter your name: ' name\necho \$name"));
echo "Hello $name, this is PHP speaking\n";
exit;
Checkout this answer's source in blog post How can I capture user input from the cmd line using PHP?.
Basically you read from standard input. See Input/output streams.

timeout for fread

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!

Categories