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.
#define DESC_PIPE 1
#define DESC_FILE 2
+#define DESC_REDIRECT 3
#define DESC_PARENT_MODE_WRITE 8
struct php_proc_open_descriptor_item {
#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) {
--- /dev/null
+--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"
--- /dev/null
+--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