Support redirect+null descriptors in proc_open

This adds support for doing something like:

    [1 => ['pipe', 'w'], 2 => ['redirect', 1]]

This will make descriptor 2 on the child end a dup'd descriptor 1.
This is mainly useful in conjunction with shell-less mode, because
we don't have an easy way to do "2>&1" there.

Additionally we support:

    [1 => ['pipe', 'w'], 2 => ['null']]

Which would be the same as a >/dev/null or >nul redirect, depending
on platform.
This commit is contained in:
Nikita Popov 2019-07-05 17:41:59 +02:00
parent 42cac9d7d7
commit 6285bb52fa
4 changed files with 189 additions and 0 deletions

View File

@ -304,6 +304,13 @@ PHP 7.4 UPGRADE NOTES
proc_open(['php', '-r', 'echo "Hello World\n";'], $descriptors, $pipes);
. proc_open() now supports "redirect" and "null" descriptors. For example:
// Like 2>&1 on the shell
proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
// Like 2>/dev/null or 2>nul on the shell
proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['null']], $pipes);
. password_hash() has argon2i(d) implementations from ext/sodium when PHP is
built without libargon.

View File

@ -390,6 +390,7 @@ static inline HANDLE dup_fd_as_handle(int fd)
#define DESC_PIPE 1
#define DESC_FILE 2
#define DESC_REDIRECT 3
#define DESC_PARENT_MODE_WRITE 8
struct php_proc_open_descriptor_item {
@ -760,6 +761,85 @@ PHP_FUNCTION(proc_open)
#else
descriptors[ndesc].childend = fd;
#endif
} else if (strcmp(Z_STRVAL_P(ztype), "redirect") == 0) {
zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
struct php_proc_open_descriptor_item *target = NULL;
php_file_descriptor_t childend;
if (!ztarget) {
php_error_docref(NULL, E_WARNING, "Missing redirection target");
goto exit_fail;
}
if (Z_TYPE_P(ztarget) != IS_LONG) {
php_error_docref(NULL, E_WARNING, "Redirection target must be an integer");
goto exit_fail;
}
for (i = 0; i < ndesc; i++) {
if (descriptors[i].index == Z_LVAL_P(ztarget)) {
target = &descriptors[i];
break;
}
}
if (target) {
childend = target->childend;
} else {
if (Z_LVAL_P(ztarget) < 0 || Z_LVAL_P(ztarget) > 2) {
php_error_docref(NULL, E_WARNING,
"Redirection target " ZEND_LONG_FMT " not found", Z_LVAL_P(ztarget));
goto exit_fail;
}
/* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
* which happens whenever an explicit override is not provided. */
#ifndef PHP_WIN32
childend = Z_LVAL_P(ztarget);
#else
switch (Z_LVAL_P(ztarget)) {
case 0: childend = GetStdHandle(STD_INPUT_HANDLE); break;
case 1: childend = GetStdHandle(STD_OUTPUT_HANDLE); break;
case 2: childend = GetStdHandle(STD_ERROR_HANDLE); break;
EMPTY_SWITCH_DEFAULT_CASE()
}
#endif
}
#ifdef PHP_WIN32
descriptors[ndesc].childend = dup_handle(childend, TRUE, FALSE);
if (descriptors[ndesc].childend == NULL) {
php_error_docref(NULL, E_WARNING,
"Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
goto exit_fail;
}
#else
descriptors[ndesc].childend = dup(childend);
if (descriptors[ndesc].childend < 0) {
php_error_docref(NULL, E_WARNING,
"Failed to dup() for descriptor " ZEND_LONG_FMT " - %s",
nindex, strerror(errno));
goto exit_fail;
}
#endif
descriptors[ndesc].mode = DESC_REDIRECT;
} else if (strcmp(Z_STRVAL_P(ztype), "null") == 0) {
#ifndef PHP_WIN32
descriptors[ndesc].childend = open("/dev/null", O_RDWR);
if (descriptors[ndesc].childend < 0) {
php_error_docref(NULL, E_WARNING,
"Failed to open /dev/null - %s", strerror(errno));
goto exit_fail;
}
#else
descriptors[ndesc].childend = CreateFileA(
"nul", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
descriptors[ndesc].childend = VCWD_OPEN("nul", O_RDWR);
if (descriptors[ndesc].childend == NULL) {
php_error_docref(NULL, E_WARNING, "Failed to open nul");
goto exit_fail;
}
#endif
descriptors[ndesc].mode = DESC_FILE;
} else if (strcmp(Z_STRVAL_P(ztype), "pty") == 0) {
#if PHP_CAN_DO_PTS
if (dev_ptmx == -1) {

View File

@ -0,0 +1,30 @@
--TEST--
Null pipes in proc_open()
--FILE--
<?php
$php = getenv('TEST_PHP_EXECUTABLE');
$cmd = [$php, '-r', 'echo "Test"; fprintf(STDERR, "Error");'];
$proc = proc_open($cmd, [1 => ['null'], 2 => ['pipe', 'w']], $pipes);
var_dump($pipes);
var_dump(stream_get_contents($pipes[2]));
proc_close($proc);
$proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['null']], $pipes);
var_dump($pipes);
var_dump(stream_get_contents($pipes[1]));
proc_close($proc);
?>
--EXPECT--
array(1) {
[2]=>
resource(4) of type (stream)
}
string(5) "Error"
array(1) {
[1]=>
resource(6) of type (stream)
}
string(4) "Test"

View File

@ -0,0 +1,72 @@
--TEST--
Redirection support in proc_open
--FILE--
<?php
$php = getenv('TEST_PHP_EXECUTABLE');
var_dump(proc_open([$php], [['redirect']], $pipes));
var_dump(proc_open([$php], [['redirect', 'foo']], $pipes));
var_dump(proc_open([$php], [['redirect', 42]], $pipes));
echo "\nWith pipe:\n";
$cmd = [$php, '-r', 'echo "Test\n"; fprintf(STDERR, "Error");'];
$proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
var_dump($pipes);
var_dump(stream_get_contents($pipes[1]));
proc_close($proc);
echo "\nWith filename:\n";
$fileName = __DIR__ . '/proc_open_redirect.txt';
$proc = proc_open($cmd, [1 => ['file', $fileName, 'w'], 2 => ['redirect', 1]], $pipes);
var_dump($pipes);
proc_close($proc);
var_dump(file_get_contents($fileName));
unlink($fileName);
echo "\nWith file:\n";
$file = fopen($fileName, 'w');
$proc = proc_open($cmd, [1 => $file, 2 => ['redirect', 1]], $pipes);
var_dump($pipes);
proc_close($proc);
fclose($file);
var_dump(file_get_contents($fileName));
unlink($fileName);
echo "\nWith inherited stdout:\n";
$proc = proc_open($cmd, [2 => ['redirect', 1]], $pipes);
proc_close($proc);
?>
--EXPECTF--
Warning: proc_open(): Missing redirection target in %s on line %d
bool(false)
Warning: proc_open(): Redirection target must be an integer in %s on line %d
bool(false)
Warning: proc_open(): Redirection target 42 not found in %s on line %d
bool(false)
With pipe:
array(1) {
[1]=>
resource(4) of type (stream)
}
string(10) "Test
Error"
With filename:
array(0) {
}
string(10) "Test
Error"
With file:
array(0) {
}
string(10) "Test
Error"
With inherited stdout:
Test
Error