Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement stream_vt100_support user function #2103

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a0e0d8d
Add VT100 support for Windows
mlocati Aug 29, 2016
18c5c42
Fix function names prefix
mlocati Aug 30, 2016
be50062
Use Unicode version of GetFinalPathNameByHandle
mlocati Aug 30, 2016
e11f3d2
Use EG(windows_version_info) instead of RtlGetVersion
mlocati Aug 30, 2016
6953d2f
Use the specified handle_id instead of STD_OUTPUT_HANDLE
mlocati Aug 30, 2016
bd93781
Switch from stream name to stream resource
mlocati Aug 30, 2016
35bf19a
Allow running tests capturing only stdout and/or stderr
mlocati Aug 30, 2016
d9b1c99
Add tests for stream_vt100_support function
mlocati Aug 30, 2016
2f05643
Export Win32 console functions
mlocati Oct 4, 2016
694e207
Fix x64 build
mlocati Oct 4, 2016
4f4ffd0
Use zend_long instead of long long, use GetConsole instead of GetFina…
mlocati Oct 4, 2016
3fcdc64
Always use zend_long on any platform
mlocati Oct 4, 2016
99bd01e
Use _get_osfhandle to determine the standard handle
mlocati Oct 5, 2016
7b155f4
Accept stream names
mlocati Oct 5, 2016
08e75a2
Raise warnings in case of invalid stream parameter
mlocati Oct 5, 2016
c10467e
Return true if disabling VT100 support on a not-console/redirected st…
mlocati Oct 6, 2016
dd85825
Remove php_win32_console_os_supports_vt100
mlocati Oct 6, 2016
a6e88f6
Differentiate stdin vs stdout/stderr
mlocati Oct 6, 2016
1643715
Simplify setting flag
mlocati Oct 6, 2016
f86567e
Allow avoid piping STDIN
mlocati Oct 6, 2016
658ff84
Let stream_vt100_support accept only resources
mlocati Oct 6, 2016
d9e0531
Fix run-tests
mlocati Oct 6, 2016
157f8e8
Revert console flags in case of failure
mlocati Oct 6, 2016
76411a4
Simplify logic of stream_vt100_support when setting the flag
mlocati Oct 7, 2016
4d956f2
Drop support for STDIN
mlocati Oct 7, 2016
7d348a0
More comprehensive tests for stream_vt100_support
mlocati Oct 7, 2016
4588b50
Remove old tests
mlocati Oct 8, 2016
bf60a78
Fix name of included file and use absolute paths
mlocati Oct 8, 2016
5d2ecf9
Enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on Windows by default
mlocati Oct 8, 2016
7581767
Remove tests for stream_vt100_support
mlocati Oct 9, 2016
d15063c
Split stream_vt100_support into stream_isatty+sapi_windows_vt100_support
mlocati Oct 9, 2016
65ad779
Add tests for stream_isatty
mlocati Oct 9, 2016
67e1eb7
Add tests for sapi_windows_vt100_support
mlocati Oct 9, 2016
f9ff470
Return null from stream_isatty is neither Windows nor Posix
mlocati Oct 14, 2016
6df8c3c
Fallback to S_ISCHR if neither Windows nor Posix
mlocati Oct 14, 2016
1081aa8
Avoid defining argc since it's only used once
mlocati Oct 17, 2016
e4aaa27
Better comment about php_win32_console_fileno_is_console
mlocati Oct 17, 2016
2d062f3
Use events instead of cNumberOfEvents
mlocati Oct 17, 2016
d70a7f2
Do not restore previous console mode
mlocati Oct 17, 2016
c2988ea
Merge master into vt100-windows
mlocati Oct 17, 2016
d1772e6
Don't configure STDOUT/STDERR on Windows with PHP_CLI_WIN32_NO_CONSOLE
mlocati Oct 17, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,17 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_supports_lock, 0, 0, 1)
ZEND_ARG_INFO(0, stream)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_isatty, 0, 0, 1)
ZEND_ARG_INFO(0, stream)
ZEND_END_ARG_INFO()

#ifdef PHP_WIN32
ZEND_BEGIN_ARG_INFO_EX(arginfo_sapi_windows_vt100_support, 0, 0, 1)
ZEND_ARG_INFO(0, stream)
ZEND_ARG_INFO(0, enable)
ZEND_END_ARG_INFO()
#endif

ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_select, 0, 0, 4)
ZEND_ARG_INFO(1, read_streams) /* ARRAY_INFO(1, read_streams, 1) */
ZEND_ARG_INFO(1, write_streams) /* ARRAY_INFO(1, write_streams, 1) */
Expand Down Expand Up @@ -3134,6 +3145,10 @@ const zend_function_entry basic_functions[] = { /* {{{ */
PHP_FE(stream_copy_to_stream, arginfo_stream_copy_to_stream)
PHP_FE(stream_get_contents, arginfo_stream_get_contents)
PHP_FE(stream_supports_lock, arginfo_stream_supports_lock)
PHP_FE(stream_isatty, arginfo_stream_isatty)
#ifdef PHP_WIN32
PHP_FE(sapi_windows_vt100_support, arginfo_sapi_windows_vt100_support)
#endif
PHP_FE(fgetcsv, arginfo_fgetcsv)
PHP_FE(fputcsv, arginfo_fputcsv)
PHP_FE(flock, arginfo_flock)
Expand Down
116 changes: 116 additions & 0 deletions ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ typedef unsigned long long php_timeout_ull;
#else
#include "win32/select.h"
#include "win32/sockets.h"
#include "win32/console.h"
typedef unsigned __int64 php_timeout_ull;
#endif

Expand Down Expand Up @@ -1635,6 +1636,121 @@ PHP_FUNCTION(stream_supports_lock)
RETURN_TRUE;
}

/* {{{ proto proto stream_isatty(resource stream)
Check if a stream is a TTY.
*/
PHP_FUNCTION(stream_isatty)
{
zval *zsrc;
php_stream *stream;
zend_long fileno;

int argc = ZEND_NUM_ARGS();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only use this in the call to zend_parse_parameters now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 1081aa8


if (zend_parse_parameters(argc, "r", &zsrc) == FAILURE) {
RETURN_FALSE;
}

php_stream_from_zval(stream, zsrc);

if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The void* cast will cause the stack corruption on 64-bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The void* cast will cause the stack corruption on 64-bit.

Fixed in 694e207

}
else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
}
else {
RETURN_FALSE;
}

#ifdef PHP_WIN32
/* Check if the Windows standard handle is redirected to file */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be /* Check if the file descriptor is a console */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better: e4aaa27

if (php_win32_console_fileno_is_console(fileno)) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
#elif HAVE_POSIX
/* Check if the file descriptor identifier is a terminal */
if (isatty(fileno)) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
#else
zend_stat_t stat;
if (zend_fstat(fileno, &stat) == 0) {
if ((stat.st_mode & /*S_IFMT*/0170000) == /*S_IFCHR*/0020000) {
RETURN_TRUE;
}
}
RETURN_NULL();
#endif
}

#ifdef PHP_WIN32
/* {{{ proto proto sapi_windows_vt100_support(resource stream[, bool enable])
Get or set VT100 support for the specified stream associated to an
output buffer of a Windows console.
*/
PHP_FUNCTION(sapi_windows_vt100_support)
{
zval *zsrc;
php_stream *stream;
zend_bool enable;
zend_long fileno;

int argc = ZEND_NUM_ARGS();

if (zend_parse_parameters(argc, "r|b", &zsrc, &enable) == FAILURE) {
RETURN_FALSE;
}

php_stream_from_zval(stream, zsrc);

if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
}
else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
}
else {
zend_internal_type_error(
ZEND_ARG_USES_STRICT_TYPES(),
"%s() was not able to analyze the specified stream",
get_active_function_name()
);
RETURN_FALSE;
}

/* Check if the Windows standard handle is redirected to file */
if (!php_win32_console_fileno_is_console(fileno)) {
RETURN_FALSE;
}

if (argc == 1) {
/* Check if the Windows standard handle has VT100 control codes enabled */
if (php_win32_console_fileno_has_vt100(fileno)) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
}
else {
/* Enable/disable VT100 control codes support for the specified Windows standard handle */
if (php_win32_console_fileno_set_vt100(fileno, enable ? TRUE : FALSE)) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
}
}
#endif

#ifdef HAVE_SHUTDOWN
/* {{{ proto int stream_socket_shutdown(resource stream, int how)
causes all or part of a full-duplex connection on the socket associated
Expand Down
4 changes: 4 additions & 0 deletions ext/standard/streamsfuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ PHP_FUNCTION(stream_socket_shutdown);
PHP_FUNCTION(stream_resolve_include_path);
PHP_FUNCTION(stream_is_local);
PHP_FUNCTION(stream_supports_lock);
PHP_FUNCTION(stream_isatty);
#ifdef PHP_WIN32
PHP_FUNCTION(sapi_windows_vt100_support);
#endif

#if HAVE_SOCKETPAIR
PHP_FUNCTION(stream_socket_pair);
Expand Down
65 changes: 47 additions & 18 deletions run-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ function error_report($testname, $logname, $tested)
}
}

function system_with_timeout($commandline, $env = null, $stdin = null)
function system_with_timeout($commandline, $env = null, $stdin = null, $captureStdIn = true, $captureStdOut = true, $captureStdErr = true)
{
global $leak_check, $cwd;

Expand All @@ -1073,21 +1073,29 @@ function system_with_timeout($commandline, $env = null, $stdin = null)
$bin_env[$key] = $value;
}

$proc = proc_open($commandline, array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
), $pipes, $cwd, $bin_env, array('suppress_errors' => true, 'binary_pipes' => true));
$descriptorspec = array();
if ($captureStdIn) {
$descriptorspec[0] = array('pipe', 'r');
}
if ($captureStdOut) {
$descriptorspec[1] = array('pipe', 'w');
}
if ($captureStdErr) {
$descriptorspec[2] = array('pipe', 'w');
}
$proc = proc_open($commandline, $descriptorspec, $pipes, $cwd, $bin_env, array('suppress_errors' => true, 'binary_pipes' => true));

if (!$proc) {
return false;
}

if (!is_null($stdin)) {
fwrite($pipes[0], $stdin);
if ($captureStdIn) {
if (!is_null($stdin)) {
fwrite($pipes[0], $stdin);
}
fclose($pipes[0]);
unset($pipes[0]);
}
fclose($pipes[0]);
unset($pipes[0]);

$timeout = $leak_check ? 300 : (isset($env['TEST_TIMEOUT']) ? $env['TEST_TIMEOUT'] : 60);

Expand All @@ -1107,7 +1115,13 @@ function system_with_timeout($commandline, $env = null, $stdin = null)
proc_terminate($proc, 9);
return $data;
} else if ($n > 0) {
$line = fread($pipes[1], 8192);
if ($captureStdOut) {
$line = fread($pipes[1], 8192);
} elseif ($captureStdErr) {
$line = fread($pipes[2], 8192);
} else {
$line = '';
}
if (strlen($line) == 0) {
/* EOF */
break;
Expand Down Expand Up @@ -1338,6 +1352,21 @@ function run_test($php, $file, $env)
return 'BORKED';
}

if (isset($section_text['CAPTURE_STDIO'])) {
$captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false;
$captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false;
$captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false;
} else {
$captureStdIn = true;
$captureStdOut = true;
$captureStdErr = true;
}
if ($captureStdOut && $captureStdErr) {
$cmdRedirect = ' 2>&1';
} else {
$cmdRedirect = '';
}

$tested = trim($section_text['TEST']);

/* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */
Expand Down Expand Up @@ -1750,7 +1779,7 @@ function run_test($php, $file, $env)
}

save_text($tmp_post, $request);
$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";

} elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) {

Expand Down Expand Up @@ -1784,7 +1813,7 @@ function run_test($php, $file, $env)
}

save_text($tmp_post, $request);
$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";

} else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {

Expand All @@ -1801,7 +1830,7 @@ function run_test($php, $file, $env)
$env['CONTENT_LENGTH'] = $content_length;
}

$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";

} else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) {

Expand All @@ -1816,7 +1845,7 @@ function run_test($php, $file, $env)
$env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$env['CONTENT_LENGTH'] = $content_length;

$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";

} else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) {
$post = trim($section_text['DEFLATE_POST']);
Expand All @@ -1829,15 +1858,15 @@ function run_test($php, $file, $env)
$env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$env['CONTENT_LENGTH'] = $content_length;

$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";

} else {

$env['REQUEST_METHOD'] = 'GET';
$env['CONTENT_TYPE'] = '';
$env['CONTENT_LENGTH'] = '';

$cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args 2>&1";
$cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect";
}

if ($leak_check) {
Expand Down Expand Up @@ -1873,7 +1902,7 @@ function run_test($php, $file, $env)

junit_start_timer($shortname);

$out = system_with_timeout($cmd, $env, isset($section_text['STDIN']) ? $section_text['STDIN'] : null);
$out = system_with_timeout($cmd, $env, isset($section_text['STDIN']) ? $section_text['STDIN'] : null, $captureStdIn, $captureStdOut, $captureStdErr);

junit_finish_timer($shortname);

Expand Down
9 changes: 9 additions & 0 deletions sapi/cli/php_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#ifdef PHP_WIN32
#include "win32/time.h"
#include "win32/signal.h"
#include "win32/console.h"
#include <process.h>
#include <shellapi.h>
#endif
Expand Down Expand Up @@ -243,6 +244,9 @@ static void print_extensions(void) /* {{{ */
#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif
#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif

static inline int sapi_cli_select(int fd)
{
Expand Down Expand Up @@ -1208,6 +1212,11 @@ int main(int argc, char *argv[])
*/
argv = save_ps_args(argc, argv);

#ifdef PHP_WIN32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs an #ifndef PHP_CLI_WIN32_NO_CONSOLE guard.

Maybe it is worth incorporating a few lines down in the #ifdef PHP_WIN32 block that call setmode()? However to keep things consistent you would need to replace the _fileno(std...) calls with the STD..._FILENO defines.

Copy link
Contributor Author

@mlocati mlocati Oct 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs an #ifndef PHP_CLI_WIN32_NO_CONSOLE guard.

I don't know PHP internals that much, but a 3rd party app could attach a console to a php.exe compiled with PHP_CLI_WIN32_NO_CONSOLE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call setmode()?

Why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps (why would you do that?), but I was basing this on the fact that the new codepage stuff uses this guard. Also, _get_osfhandle returns -2 if the handle is invalid (rather than -1).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re setmode() - I meant in the block of code that calls setmode, just to keep the PHP_WIN32 startup stuff together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re setmode() - I meant in the block of code that calls setmode, just to keep the PHP_WIN32 startup stuff together.

They have different and unrelated purposes, I don't see why we have the necessity to keep them together.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP_CLI_WIN32_NO_CONSOLE builds a binary without any interaction, which is meant to be run a a service for example. You can check sapi/cli/config.w32. Though i'm not sure how often this feature is actually used, I don't recall any related bug reports within last couple of years. Anyway there is supposed to be no console interaction. Except maybe that the service is allowed to communicate with desktop, but even in that case - wouldn't the descriptors be redirected?

Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even in that case, wouldn't the descriptors be redirected?

I guess so... d1772e6

php_win32_console_fileno_set_vt100(STDOUT_FILENO, TRUE);
php_win32_console_fileno_set_vt100(STDERR_FILENO, TRUE);
#endif

cli_sapi_module.additional_functions = additional_functions;

#if defined(PHP_WIN32) && defined(_DEBUG) && defined(PHP_WIN32_DEBUG_HEAP)
Expand Down
Loading