Skip to content

Commit

Permalink
Add VT100 support for Windows
Browse files Browse the repository at this point in the history
Fix function names prefix

Use Unicode version of GetFinalPathNameByHandle

Use EG(windows_version_info) instead of RtlGetVersion

Use the specified handle_id instead of STD_OUTPUT_HANDLE

Switch from stream name to stream resource

Allow running tests capturing only stdout and/or stderr

Add tests for stream_vt100_support function

Export Win32 console functions

Fix x64 build

Use zend_long instead of long long, use GetConsole instead of GetFinalPathNameByHandleW to check if a handle is a valid console stream

Always use zend_long on any platform

Use _get_osfhandle to determine the standard handle

Accept stream names

Raise warnings in case of invalid stream parameter

Return true if disabling VT100 support on a not-console/redirected stream or on old Windows versions

Remove php_win32_console_os_supports_vt100

Differentiate stdin vs stdout/stderr

Simplify setting flag

Allow avoid piping STDIN

Let stream_vt100_support accept only resources

Fix run-tests

Revert console flags in case of failure

Simplify logic of stream_vt100_support when setting the flag

Return true if succeeded, false otherwise

Drop support for STDIN

More comprehensive tests for stream_vt100_support

Remove old tests

Fix name of included file and use absolute paths

Enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on Windows by default

Remove tests for stream_vt100_support

Split stream_vt100_support into stream_isatty+sapi_windows_vt100_support

Add tests for stream_isatty

Add tests for sapi_windows_vt100_support

Return null from stream_isatty is neither Windows nor Posix

Fallback to S_ISCHR if neither Windows nor Posix

Avoid defining argc since it's only used once

Better comment about php_win32_console_fileno_is_console

Use events instead of cNumberOfEvents

Do not restore previous console mode

We need to restore previous console mode on failing SetConsole calls only for STDIN

Don't configure STDOUT/STDERR on Windows with PHP_CLI_WIN32_NO_CONSOLE
  • Loading branch information
mlocati authored and weltling committed Oct 28, 2016
1 parent 946eb9b commit 33301d5
Show file tree
Hide file tree
Showing 29 changed files with 2,354 additions and 20 deletions.
15 changes: 15 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,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 @@ -3146,6 +3157,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
114 changes: 114 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 @@ -1569,6 +1570,119 @@ 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;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "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);
}
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 */
if (php_win32_console_fileno_is_console(fileno)) {
RETURN_TRUE;
}
else {
RETURN_FALSE;
}
#elif HAVE_POSIX

This comment has been minimized.

Copy link
@remicollet

remicollet Apr 12, 2017

Member

@weltling @mlocati if this test really OK ?

HAVE_POSIX means with the posix extension.
Isn't the isatty always available ? (from unistd.h)

BTW the new tests are failing with ./configure --disable-all.

This comment has been minimized.

Copy link
@remicollet

remicollet Apr 12, 2017

Member

Isn't better to use ?

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

This comment has been minimized.

Copy link
@remicollet

remicollet Apr 12, 2017

Member

See 04fe5e9

/* 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;
}

This comment has been minimized.

Copy link
@remicollet

remicollet Apr 12, 2017

Member

@mlocati it seems the else RETURN_FALSE is missing (else NULL also breaks the tests)

This comment has been minimized.

Copy link
@weltling

weltling Apr 13, 2017

Contributor

Addressed in 858ad8a and df3e1a1. Thanks!

}
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 file descriptor is a console */
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 @@ -1057,7 +1057,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 @@ -1068,21 +1068,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 @@ -1102,7 +1110,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 @@ -1332,6 +1346,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 @@ -1740,7 +1769,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 @@ -1774,7 +1803,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 @@ -1791,7 +1820,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 @@ -1806,7 +1835,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 @@ -1819,15 +1848,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 @@ -1863,7 +1892,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);

#if defined(PHP_WIN32) && !defined(PHP_CLI_WIN32_NO_CONSOLE)
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

0 comments on commit 33301d5

Please sign in to comment.