Skip to content

Commit 2dbde18

Browse files
authored
Fix GH-8952: std streams can not be deliberately closed (#8953)
1 parent 5e0b2e5 commit 2dbde18

File tree

4 files changed

+148
-8
lines changed

4 files changed

+148
-8
lines changed

sapi/cli/php_cli.c

+6-8
Original file line numberDiff line numberDiff line change
@@ -538,21 +538,19 @@ static void cli_register_file_handles(bool no_close) /* {{{ */
538538
s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out);
539539
s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err);
540540

541-
/* Release stream resources, but don't free the underlying handles. Othewrise,
542-
* extensions which write to stderr or company during mshutdown/gshutdown
543-
* won't have the expected functionality.
544-
*/
545-
if (s_in) s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
546-
if (s_out) s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
547-
if (s_err) s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
548-
549541
if (s_in==NULL || s_out==NULL || s_err==NULL) {
550542
if (s_in) php_stream_close(s_in);
551543
if (s_out) php_stream_close(s_out);
552544
if (s_err) php_stream_close(s_err);
553545
return;
554546
}
555547

548+
if (no_close) {
549+
s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
550+
s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
551+
s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
552+
}
553+
556554
s_in_process = s_in;
557555

558556
php_stream_to_zval(s_in, &ic.value);

sapi/cli/tests/gh8827-001.phpt

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
std handles can be deliberately closed 001
3+
--SKIPIF--
4+
<?php
5+
if (php_sapi_name() != "cli") {
6+
die("skip CLI only");
7+
}
8+
if (PHP_OS_FAMILY == 'Windows') {
9+
die("skip not for Windows");
10+
}
11+
if (PHP_DEBUG) {
12+
die("skip std streams are not closeable in debug builds");
13+
}
14+
if (getenv('SKIP_REPEAT')) {
15+
die("skip cannot be repeated");
16+
}
17+
?>
18+
--FILE--
19+
<?php
20+
print "STDIN:\n";
21+
fclose(STDIN);
22+
var_dump(@fopen('php://stdin', 'r'));
23+
24+
print "STDERR:\n";
25+
fclose(STDERR);
26+
var_dump(@fopen('php://stderr', 'a'));
27+
28+
print "STDOUT:\n";
29+
fclose(STDOUT);
30+
// not printed if stdout is closed
31+
var_dump(@fopen('php://stdout', 'a'));
32+
?>
33+
--EXPECT--
34+
STDIN:
35+
bool(false)
36+
STDERR:
37+
bool(false)
38+
STDOUT:

sapi/cli/tests/gh8827-002.phpt

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
std handles can be deliberately closed 002
3+
--SKIPIF--
4+
<?php
5+
if (php_sapi_name() != "cli") {
6+
die("skip CLI only");
7+
}
8+
if (PHP_OS_FAMILY == 'Windows') {
9+
die("skip not for Windows");
10+
}
11+
if (PHP_DEBUG) {
12+
die("skip std streams are not closeable in debug builds");
13+
}
14+
if (getenv('SKIP_REPEAT')) {
15+
die("skip cannot be repeated");
16+
}
17+
?>
18+
--FILE--
19+
<?php
20+
21+
$stdin = fopen('php://stdin', 'r');
22+
$stdout = fopen('php://stdout', 'r');
23+
$stderr = fopen('php://stderr', 'r');
24+
25+
ob_start(function ($buffer) use ($stdout) {
26+
fwrite($stdout, $buffer);
27+
}, 1);
28+
29+
print "STDIN:\n";
30+
fclose(STDIN);
31+
var_dump(@fopen('php://stdin', 'r'));
32+
33+
print "STDERR:\n";
34+
fclose(STDERR);
35+
var_dump(@fopen('php://stderr', 'a'));
36+
37+
print "STDOUT:\n";
38+
fclose(STDOUT);
39+
var_dump(@fopen('php://stdout', 'a'));
40+
?>
41+
--EXPECT--
42+
STDIN:
43+
bool(false)
44+
STDERR:
45+
bool(false)
46+
STDOUT:
47+
bool(false)

sapi/cli/tests/gh8827-003.phpt

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
std handles can be deliberately closed 003
3+
--SKIPIF--
4+
<?php
5+
if (php_sapi_name() != "cli") {
6+
die("skip CLI only");
7+
}
8+
if (PHP_OS_FAMILY == 'Windows') {
9+
die("skip not for Windows");
10+
}
11+
if (PHP_DEBUG) {
12+
die("skip std streams are not closeable in debug builds");
13+
}
14+
if (getenv('SKIP_REPEAT')) {
15+
die("skip cannot be repeated");
16+
}
17+
?>
18+
--FILE--
19+
<?php
20+
21+
$stdoutStream = fopen('php://stdout', 'r');
22+
23+
$stdoutFile = tempnam(sys_get_temp_dir(), 'gh8827');
24+
$stderrFile = tempnam(sys_get_temp_dir(), 'gh8827');
25+
register_shutdown_function(function () use ($stdoutFile, $stderrFile) {
26+
unlink($stdoutFile);
27+
unlink($stderrFile);
28+
});
29+
30+
fclose(STDOUT);
31+
fclose(STDERR);
32+
33+
$stdoutFileStream = fopen($stdoutFile, 'a');
34+
$stderrFileStream = fopen($stderrFile, 'a');
35+
36+
print "Goes to stdoutFile\n";
37+
file_put_contents('php://fd/1', "Also goes to stdoutFile\n");
38+
39+
file_put_contents('php://fd/2', "Goes to stderrFile\n");
40+
41+
ob_start(function ($buffer) use ($stdoutStream) {
42+
fwrite($stdoutStream, $buffer);
43+
}, 1);
44+
45+
print "stdoutFile:\n";
46+
readfile($stdoutFile);
47+
48+
print "stderrFile:\n";
49+
readfile($stderrFile);
50+
51+
?>
52+
--EXPECT--
53+
stdoutFile:
54+
Goes to stdoutFile
55+
Also goes to stdoutFile
56+
stderrFile:
57+
Goes to stderrFile

0 commit comments

Comments
 (0)