From 6285bb52faf407b07e71497723d13a1b08821352 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 5 Jul 2019 17:41:59 +0200 Subject: [PATCH] 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. --- UPGRADING | 7 ++ ext/standard/proc_open.c | 80 +++++++++++++++++++ .../general_functions/proc_open_null.phpt | 30 +++++++ .../general_functions/proc_open_redirect.phpt | 72 +++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 ext/standard/tests/general_functions/proc_open_null.phpt create mode 100644 ext/standard/tests/general_functions/proc_open_redirect.phpt diff --git a/UPGRADING b/UPGRADING index a72e17c496..9bac919b98 100644 --- 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. diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 9a0bd94199..82662dfa27 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -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 index 0000000000..097d4d2892 --- /dev/null +++ b/ext/standard/tests/general_functions/proc_open_null.phpt @@ -0,0 +1,30 @@ +--TEST-- +Null pipes in proc_open() +--FILE-- + ['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 index 0000000000..92da696ee9 --- /dev/null +++ b/ext/standard/tests/general_functions/proc_open_redirect.phpt @@ -0,0 +1,72 @@ +--TEST-- +Redirection support in proc_open +--FILE-- + ['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 -- 2.50.1