]> granicus.if.org Git - php/commitdiff
Support redirect+null descriptors in proc_open
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 5 Jul 2019 15:41:59 +0000 (17:41 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Thu, 11 Jul 2019 13:48:10 +0000 (15:48 +0200)
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.

UPGRADING
ext/standard/proc_open.c
ext/standard/tests/general_functions/proc_open_null.phpt [new file with mode: 0644]
ext/standard/tests/general_functions/proc_open_redirect.phpt [new file with mode: 0644]

index a72e17c496affbc38e1e9dbd994e64abc75b9dd4..9bac919b9842e1dfc35582488cc1cba9f8ad806b 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -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.
 
index 9a0bd941993ad944d8c48a7d43abb744489cd49f..82662dfa275b7d184bf7174ad62f7b55c7483c06 100644 (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) {
diff --git a/ext/standard/tests/general_functions/proc_open_null.phpt b/ext/standard/tests/general_functions/proc_open_null.phpt
new file mode 100644 (file)
index 0000000..097d4d2
--- /dev/null
@@ -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"
diff --git a/ext/standard/tests/general_functions/proc_open_redirect.phpt b/ext/standard/tests/general_functions/proc_open_redirect.phpt
new file mode 100644 (file)
index 0000000..92da696
--- /dev/null
@@ -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