Skip to content

Fix #8952: std streams can not be deliberately closed #8953

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

Merged
merged 2 commits into from
Jul 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 6 additions & 8 deletions sapi/cli/php_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,21 +538,19 @@ static void cli_register_file_handles(bool no_close) /* {{{ */
s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out);
s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err);

/* Release stream resources, but don't free the underlying handles. Othewrise,
* extensions which write to stderr or company during mshutdown/gshutdown
* won't have the expected functionality.
*/
if (s_in) s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
if (s_out) s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
if (s_err) s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;

if (s_in==NULL || s_out==NULL || s_err==NULL) {
if (s_in) php_stream_close(s_in);
if (s_out) php_stream_close(s_out);
if (s_err) php_stream_close(s_err);
return;
}

if (no_close) {
s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
}

s_in_process = s_in;

php_stream_to_zval(s_in, &ic.value);
Expand Down
38 changes: 38 additions & 0 deletions sapi/cli/tests/gh8827-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
std handles can be deliberately closed 001
--SKIPIF--
<?php
if (php_sapi_name() != "cli") {
die("skip CLI only");
}
if (PHP_OS_FAMILY == 'Windows') {
die("skip not for Windows");
}
if (PHP_DEBUG) {
die("skip std streams are not closeable in debug builds");
}
if (getenv('SKIP_REPEAT')) {
die("skip cannot be repeated");
}
?>
--FILE--
<?php
print "STDIN:\n";
fclose(STDIN);
var_dump(@fopen('php://stdin', 'r'));

print "STDERR:\n";
fclose(STDERR);
var_dump(@fopen('php://stderr', 'a'));

print "STDOUT:\n";
fclose(STDOUT);
// not printed if stdout is closed
var_dump(@fopen('php://stdout', 'a'));
?>
--EXPECT--
STDIN:
bool(false)
STDERR:
bool(false)
STDOUT:
47 changes: 47 additions & 0 deletions sapi/cli/tests/gh8827-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
std handles can be deliberately closed 002
--SKIPIF--
<?php
if (php_sapi_name() != "cli") {
die("skip CLI only");
}
if (PHP_OS_FAMILY == 'Windows') {
die("skip not for Windows");
}
if (PHP_DEBUG) {
die("skip std streams are not closeable in debug builds");
}
if (getenv('SKIP_REPEAT')) {
die("skip cannot be repeated");
}
?>
--FILE--
<?php

$stdin = fopen('php://stdin', 'r');
$stdout = fopen('php://stdout', 'r');
$stderr = fopen('php://stderr', 'r');

ob_start(function ($buffer) use ($stdout) {
fwrite($stdout, $buffer);
}, 1);

print "STDIN:\n";
fclose(STDIN);
var_dump(@fopen('php://stdin', 'r'));

print "STDERR:\n";
fclose(STDERR);
var_dump(@fopen('php://stderr', 'a'));

print "STDOUT:\n";
fclose(STDOUT);
var_dump(@fopen('php://stdout', 'a'));
?>
--EXPECT--
STDIN:
bool(false)
STDERR:
bool(false)
STDOUT:
bool(false)
57 changes: 57 additions & 0 deletions sapi/cli/tests/gh8827-003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
std handles can be deliberately closed 003
--SKIPIF--
<?php
if (php_sapi_name() != "cli") {
die("skip CLI only");
}
if (PHP_OS_FAMILY == 'Windows') {
die("skip not for Windows");
}
if (PHP_DEBUG) {
die("skip std streams are not closeable in debug builds");
}
if (getenv('SKIP_REPEAT')) {
die("skip cannot be repeated");
}
?>
--FILE--
<?php

$stdoutStream = fopen('php://stdout', 'r');

$stdoutFile = tempnam(sys_get_temp_dir(), 'gh8827');
$stderrFile = tempnam(sys_get_temp_dir(), 'gh8827');
register_shutdown_function(function () use ($stdoutFile, $stderrFile) {
unlink($stdoutFile);
unlink($stderrFile);
});

fclose(STDOUT);
fclose(STDERR);

$stdoutFileStream = fopen($stdoutFile, 'a');
$stderrFileStream = fopen($stderrFile, 'a');

print "Goes to stdoutFile\n";
file_put_contents('php://fd/1', "Also goes to stdoutFile\n");

file_put_contents('php://fd/2', "Goes to stderrFile\n");

ob_start(function ($buffer) use ($stdoutStream) {
fwrite($stdoutStream, $buffer);
}, 1);

print "stdoutFile:\n";
readfile($stdoutFile);

print "stderrFile:\n";
readfile($stderrFile);

?>
--EXPECT--
stdoutFile:
Goes to stdoutFile
Also goes to stdoutFile
stderrFile:
Goes to stderrFile