Why stream_select on STDIN becomes blocking when cmd.exe loses focus? - php

Goal: run a PHP file in cmd, script loops x times and on every iteration checks to see if user has entered any input (stream_select() with STDIN) and if so - pauses the loop until the user hits enter, then prints out the input and continues with iteration.
Problem: Script runs perfectly as long as cmd.exe window is in focus - when I click on another window the script pauses at stream_select and doesn't continue until I but the cmd window back in focus and send it some input (a simple enter key press would do the trick). No errors.
Question: why does losing focus on cmd affect stream_select and block the loop? ...and is there a workaround? (e.g. is it possible to check if the current cmd window is in focus?)
Code example, used cmd php script.php in working directory.
<?php
$loopCount = 20;
while ($loopCount) {
$start = microtime(true);
echo 'check on "' . $loopCount . '"' . PHP_EOL;
$stream = fopen('php://stdin', 'r');
$stream_array = array($stream);
$write = array();
$except = array();
if (stream_select($stream_array, $write, $except, 1, 0)) {
$input = trim(fgets($stream));
if ($input) {
echo 'input was "' . $input . '"' . PHP_EOL;
}
}
fclose($stream);
echo $loopCount . ' in ' . (microtime(true) - $start) . PHP_EOL;
$loopCount--;
}
Things I have tried with no luck:
moving fopen and fclose outside the loop
ignore_user_abort(1);
stream_set_blocking($stream, 0);
null, 0 and higher values for both tv_sec and tv_usec params of stream_select()
checking for connection_aborted() and connection_status()
Environment: Windows 7, XAMPP for windows, PHP 5.4.19 (cli), Zend Engine v2.4.0

I think the problem is because stream_select() will also return 1 for STDIN when the stream receives EOL, which is what I think happens when cmd loses focus.
From http://www.php.net/manual/en/function.stream-select.php
in particular, a stream resource is also ready on end-of-file, in which case an fread() will return a zero length string
When focus is not lost and no input is given, stream_select returns 0 and the $read array passed in is emptied by reference. When focus is lost OR when input is given, the $read array remains intact and you are expected to read from each index.
But that leaves us window's users in a bad situation because we don't have a way to non-blockingly read that zero length string with stream_get_(contents|line) or fread or fgets. And since we cannot differentiate between losing focus and supplying actual data I believe we have to wait for one of these bugs to get fixed:
https://bugs.php.net/bug.php?id=36030
https://bugs.php.net/bug.php?id=34972
I'm currently exploring an option to prevent the window from losing focus via the win32 API, but that looks impractical for even my needs.
Here is the workaround I'm using for now...
https://gist.github.com/anonymous/80080060869f875e7214

Related

How to get cursor position with PHP-CLI?

With a PHP script which runs in CLI mode, I want to get the cursor position in a portable way.
With the code :
// Query Cursor Position
echo "\033[6n";
In the terminal, this code reports the cursor position, as
wb ?> ./script.php
^[[5;1R
wb ?> ;1R
But, I can't retrieve the two values (row: 5, column: 1) in the code.
After some tests with output buffering :
ob_start();
echo "\033[6n";
$s = ob_get_contents();
file_put_contents('cpos.txt',$s);
I've "\033[6n" in the cpos.txt file, not the device answer.
And reading STDIN :
$timeout = 2;
$sent = false;
$t = microtime(true);
$buf = '';
stream_set_blocking(STDIN,false);
while(true){
$buf .= fread(STDIN,8);
if(!$sent){
echo "\033[6n";
$sent = true;
}
if($t+$timeout<microtime(true))
break;
}
var_dump($buf);
The buffer is empty but the terminal show the device answer :
wb ?> ./script.php
^[[5;1R
string(0) ""
wb ?>
Is there a way, without curses, to get the cursor position ?
The code you have so far almost works, and you'll find that hitting enter and waiting for your timeout to complete does produce a string containing the answer, but with a \n character on the end. (Note the string length of 7 instead of 0.)
$ php foo.php
^[[2;1R
string(7) "
"
The issue here is that stream_set_blocking does not prevent the terminal from buffering input line-by-line, so the terminal doesn't send anything to stdin of your program until the enter key is pressed.
To make the terminal send characters immediately to your program without line-buffering, you need to set the terminal to "non-canonical" mode. This disables any line-editing features, such as the ability to press backspace to erase characters, and instead sends characters to the input buffer immediately. The easiest way to do this in PHP is to call the Unix utility stty.
<?php
system('stty -icanon');
echo "\033[6n";
$buf = fread(STDIN, 16);
var_dump($buf);
This code successfully captures the response from the terminal into $buf.
$ php foo.php
^[[2;1Rstring(6) ""
However, this code has a couple of issues. First of all, it doesn't re-enable canonical mode in the terminal after it's finished. This could cause issues when trying to input from stdin later in your program, or in your shell after your program exits. Secondly, the response code from the terminal ^[[2;1R is still echoed to the terminal, which makes your program's output look messy when all you want to do is read this into a variable.
To solve the input echoing issue, we can add -echo to the stty arguments to disable input echoing in the terminal. To reset the terminal to its state before we changed it, we can call stty -g to output a list of current terminal settings which can be passed to stty later to reset the terminal.
<?php
// Save terminal settings.
$ttyprops = trim(`stty -g`);
// Disable canonical input and disable echo.
system('stty -icanon -echo');
echo "\033[6n";
$buf = fread(STDIN, 16);
// Restore terminal settings.
system("stty '$ttyprops'");
var_dump($buf);
Now when running the program, we don't see any junk displayed in the terminal:
$ php foo.php
string(6) ""
One last potential improvement we can make to this is to allow the program to be run when stdout is redirected to another process / file. This may or may not be necessary for your application, but currently, running php foo.php > /tmp/outfile will not work, as echo "\033[6n"; will write straight to the output file rather than to the terminal, leaving your program waiting for characters to be sent to stdin as the terminal was never sent any escape sequence so will not respond to it. A workaround for this is to write to /dev/tty instead of stdout as follows:
$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term); // Flush and close the file.
Putting this all together, and using bin2hex() rather than var_dump() to get a listing of characters in $buf, we get the following:
<?php
$ttyprops = trim(`stty -g`);
system('stty -icanon -echo');
$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term);
$buf = fread(STDIN, 16);
system("stty '$ttyprops'");
echo bin2hex($buf) . "\n";
We can see that the program works correctly as follows:
$ php foo.php > /tmp/outfile
$ cat /tmp/outfile
1b5b323b3152
$ xxd -p -r /tmp/outfile | xxd
00000000: 1b5b 323b 3152 .[2;1R
This shows that $buf contained ^[[2;1R, indicating the cursor was at row 2 and column 1 when its position was queried.
So now all that's left to do is to parse this string in PHP and extract the row and column separated by the semicolon. This can be done with a regex.
<?php
// Example response string.
$buf = "\033[123;456R";
$matches = [];
preg_match('/^\033\[(\d+);(\d+)R$/', $buf, $matches);
$row = intval($matches[1]);
$col = intval($matches[2]);
echo "Row: $row, Col: $col\n";
This gives the following output:
Row: 123, Col: 456
It's worth noting that all this code is only portable to Unix-like operating systems and ANSI/VT100-compatible terminals. This code may not work on Windows unless you run the program under Cygwin / MSYS2. I'd also recommend that you add some error handling to this code in case you don't get the response from the terminal that you expect for whatever reason.
(this is really a comment, but it's a bit long)
Using hard coded terminal sequences is a very long way from "portable". While most terminal emulators available currently will support ANSI, vt100 or xterm codes which have a common base there is a very well defined API for accessing interactive terminals known as "curses". A PHP extension is available in pecl. This is just a stub interface to the curses system - present on any Unix/Linux system. While it is possible to set this up on mswindows, using cygwin or pdcurses, it's not an easy fit. You omitted to mention what OS you are working on. (The mswindows console uses ANSI sequences)
There is a toolkit (hoa) based on termcap (predecessor to curses) which might be useful.
To "retrieve" the data you just need to read from stdin (although it would be advisable to uses non-blocking up for this).

PHP exec() function only runs extremely short Python scripts

I'm having some trouble using the PHP exec() function. Whenever the script I'm attempting to run is short, exec() works just fine. But if the script takes any more than a second or so, it fails. Note, I've attempted run the long script manually on the command line, and it works just fine. It seems as though the PHP interpreter is killing my external script if it takes any longer than a second or so to run. Any thoughts or suggestions? Here is my code:
<?php
$fileName = "foobar.docx";
$argVar = $fileName;
exec("python3 /var/www/html/jan8/alexandrina.py /var/www/html/jan8/$argVar");
echo "$output";
?>
And here is my script:
#!/usr/bin/env python3
import docx
import sys
docxFile = "".join(sys.argv[1:])
# The Three Lines Below Create a New Variable "htmlFile"
# "htmlFile" is the same as "docxFile", except ".docx" is cut off
# and replaced with ".html"
myNumber = len(docxFile) - 5
htmlFile = docxFile[0:myNumber]
htmlFile = htmlFile + '.html'
def generateHTML(filename):
doc = docx.Document(filename)
fullText = []
for para in doc.paragraphs:
fullText.append('<p>')
fullText.append(para.text)
fullText.append('</p>')
fullText.append('\n')
return '\n'.join(fullText)
file = open(htmlFile, "w")
file.write(generateHTML(docxFile))
file.close()
print("end python script")
Additional notes: I've increased the max execution time limits in php.ini, but I don't think that should matter, as the "long script" should only take a few seconds to run. Also, when I refer to the "short script", and the "long script", I'm actually referring to the same script. The difference between the "long script" and the "short script" is just the time to execute as it may vary depending on the size of the file I'm asking the script to process. Anyway... any suggestions would really be appreciated!
Ordinarily, php exec function should block until the command you run has completed. I.e., the PHP script will halt, waiting for the command to finish until continuing with the rest of your script. I was half thinking that your server was experiencing a max_execution_time timeout, but you've clearly stated that even just a couple of seconds is too long and even these fairly short scripts are having trouble.
A couple of solutions occur to me. The simplest one is to alter the python command so that a) any output is routed to a file or output stream and b) the process is run in the background. According to the docs on exec:
If a program is started with this function, in order for it to continue running in the background, the output of the program must be redirected to a file or another output stream. Failing to do so will cause PHP to hang until the execution of the program ends.
I also would like you to make use of the two additional optional parameters for the exec function.
$fileName = "foobar.docx";
$argVar = $fileName;
$cmd = "python3 /var/www/html/jan8/alexandrina.py /var/www/html/jan8/$argVar";
// modify your command to toss output, background the process, and output the process id
$cmd_modified = $cmd . " >/dev/null & echo \$!";
$cmd_output = NULL; // this will be an array of output
$cmd_return_value = NULL; // this will be the return value of the script
exec($cmd_modified, $cmd_output, $cmd_return_value);
echo "exec has completed</br>";
echo "output:<br>" . print_r($cmd_output, TRUE) . "<br>";
echo "return value: " . print_r($cmd_return_value, TRUE);
This may help or may not. If it does not, we still might be able to solve the problem using posix commands.
EDIT: according to crispytx, the long scripts are resulting in a $cmd_return_val of 1 which means an error is happening. Try changing this one line:
$cmd_modified = $cmd . " >/dev/null & echo \$!";
to this
$cmd_modified = $cmd . " & echo \$!";
And let us know what the output of $cmd_output is -- it should at the very least have the process id of the newly spawned process.
Thanks for all the help S. Imp. I had a little trouble debugging using your suggestions because I happened to be using AJAX to call the script. However, I wrote simpler script using your suggestions to try and debug the problem and this is what I found:
Array ( [0] => Traceback (most recent call last): [1] => File "/var/www/html/jan8/alexandrina.py", line 28, in [2] => file.write(generateHTML(docxFile)) [3] => UnicodeEncodeError: 'ascii' codec can't encode character '\u2026' in position 25: ordinal not in range(128) )
So it looks like the problem has to do with ascii encoding! Even though the larger file was just a docx file with the same text as the shorter docx file repeated over and over again for 300 pages. It seems that if a docx file exceeds 1 pages, ascii characters are inserted that aren't present in single page docx files. I have no idea if this post will ever end up helping anyone, but who knows!
[SOLVED]

getting result from Vowpal Wabbit daemon mode

I am running VW in daemon mode. As a standalone executable, it runs perfectly fine. In daemon mode, I see something about predictions and options initially but not the end result. Not sure what exactly is going on.
This is how I call VW6
/bin64/vw --daemon --num_children 2 -t -i ~/modelbow.vw6 --min_prediction 0 --max_prediction 1 -p stdout 2>&1
I check vw6 is running fine. I send data using simple php script (removed debug lines for brevity):
$fp = fsockopen("localhost",26542, $errno, $errstr, 3);
$fp_dat = fopen("/tmp/ml.dat", "r");
$mldata = explode("\n", file_get_contents("/tmp/ml.dat"));
$mlstr = implode($mldata);
fwrite($fp, $mlstr);
$result = trim(fgets($fp, 1024));
print $result;
Print $result above prints nothing. The only thing I see in stdout is
num sources = 1
Num weight bits = 28
learning rate = 10
initial_t = 1
power_t = 0.5
decay_learning_rate = 1
predictions = stdout
only testing
average since example example current current current
loss last counter weight label predict features
While in a standalone executable mode, if I run with the same model same dat file just without the -daemon option, it happily gives a result at the end
...
...
predictions = stdout
only testing
average since example example current current current
loss last counter weight label predict features
1.000000 ba66dfc7a135e2728d08010b40586b90
Any idea what could be going wrong here with the daemon mode? I tried using -p /tmp/ option as well...ran the daemon mode with sudo but nothing helped. Is there a debug dump option or verbose option or something else to know what exactly is going on?
thanks
The reason it is not working is not in vw but in the PHP client code.
explode on "\n", strips the newlines out.
implode without a glue-string parameter results in glue-string defaulting to the empty string.
Result: newlines are stripped out.
All examples are merged into one big (and incomplete, since there's no newline at the end) example.
vw needs newlines to separate examples, without them it will be waiting forever for the 1st example to complete.
So I think you need to change the implode line of code to:
$mlstr = implode("\n", $mldata);
for it to work.
You will also need an additional ending newline to get the last line through.

PHP script will not finish when stream_set_blocking is true

I'm using SSH2 to establish a stream into a device running modified linux. After the stream is established, I set blocking to true and begin reading the output. Once I see the word "Last", I know that the system is ready to accept commands, so I send one. I then read the output generated by that command.
This all works perfectly, except, I have to manually close the stream. I'm pretty sure that I'm not getting an EOF or newline back and this is probably why, however, this is all new to me so I could be wrong.
Looking to exit once the output is done.
Here is what I'm looking for before I send the first command:
Last login: Tue May 7 06:41:55 PDT 2013 from 10.150.102.115
The loop that echos the output. I have to check for the word "Last" - I ignore if it its seen more than once. (It was causing the loop to repeat.):
// Prevents premature termination
$lastCount = 1;
stream_set_blocking($stdio, true);
while($line = fgets($stdio)) {
$count++;
flush();
if (strstr($line, 'Last') && $lastCount == 1) {
fwrite($stdio,$command . PHP_EOL);
$lastCount--;
}
echo $line;
}
fclose($stdio);
Your mode is incorrect and should be set to 0.
If mode is 0, the given stream will be switched to non-blocking mode, and if 1, it will be switched to blocking mode. This affects calls like fgets() and fread() that read from the stream. In non-blocking mode an fgets() call will always return right away while in blocking mode it will wait for data to become available on the stream.
http://php.net/manual/en/function.stream-set-blocking.php
Looks like there is a trick to using blocking:
http://www.php.net/manual/en/function.stream-set-blocking.php#110755

Error retrieving exec() output

I'm using exec to grab curl output (I need to use curl as linux command).
When I start my file using php_cli I see a curl output:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 75480 100 75480 0 0 55411 0 0:00:01 0:00:01 --:--:-- 60432
It means that all the file has been downloaded correctly (~ 75 KB).
I have this code:
$page = exec('curl http://www.example.com/test.html');
I get a really strange output, I only get: </html>
(that's the end of my test.html file)
I really do not understand the reason, CURL seems to download all the file, but in $page I only get 7 characters (the lastest 7 characters).
Why?
P.S. I know I can download the source code using other php functions, but I must to use curl (as linux command).
Unless this is a really weird requirement, why not use PHP cURL library instead? You get much finer control on what happens, as well as call parameters (timeout, etc.).
If you really must use curl command line binary from PHP:
1) Use shell_exec() (this solves your problem)
2) Use 2>&1 at end of command (you might need stderr output as well as stdout)
3) Use the full path to curl utility: do not rely on PATH setting.
RTM for exec()
It returns
The last line from the result of the command.
You have to set the second parameter to exec() that will contain all the output from the command executed.
Example:
<?php
$allOutputLines = array();
$returnCode = 0;
$lastOutputLine = exec(
'curl http://www.example.com/test.html',
$allOutputLines,
$returnCode
);
echo 'The command was executed and with return code: ' . $returnCode . "\n";
echo 'The last line outputted by the command was: ' . $lastOutputLine . "\n";
echo 'The full command output was: ' . "\n";
echo implode("\n", $allOutputLines) . "\n";
?>

Categories