summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--UPGRADING8
-rw-r--r--ext/standard/proc_open.c98
-rw-r--r--ext/standard/tests/general_functions/proc_open_sockets1.inc7
-rw-r--r--ext/standard/tests/general_functions/proc_open_sockets1.phpt56
-rw-r--r--ext/standard/tests/general_functions/proc_open_sockets2.inc7
-rw-r--r--ext/standard/tests/general_functions/proc_open_sockets2.phpt67
-rw-r--r--ext/standard/tests/general_functions/proc_open_sockets3.phpt55
-rw-r--r--main/streams/plain_wrapper.c5
-rw-r--r--win32/sockets.c33
-rw-r--r--win32/sockets.h1
10 files changed, 300 insertions, 37 deletions
diff --git a/UPGRADING b/UPGRADING
index 704bd2924a..cb4021b8a5 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -631,6 +631,14 @@ PHP 8.0 UPGRADE NOTES
$proc = proc_open($command, [['pty'], ['pty'], ['pty']], $pipes);
+ . proc_open() now supports socket pair descriptors. The following attaches
+ a distinct socket pair to stdin, stdout and stderr:
+
+ $proc = proc_open(
+ $command, [['socket'], ['socket'], ['socket']], $pipes);
+
+ Unlike pipes, sockets do not suffer from blocking I/O issues on Windows.
+ However, not all programs may work correctly with stdio sockets.
. Sorting functions are now stable, which means that equal-comparing elements
will retain their original order.
RFC: https://wiki.php.net/rfc/stable_sorting
diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c
index ac00dd318c..501d2c3f35 100644
--- a/ext/standard/proc_open.c
+++ b/ext/standard/proc_open.c
@@ -444,11 +444,18 @@ static inline HANDLE dup_fd_as_handle(int fd)
# define close_descriptor(fd) close(fd)
#endif
+/* Determines the type of a descriptor item. */
+typedef enum _descriptor_type {
+ DESCRIPTOR_TYPE_STD,
+ DESCRIPTOR_TYPE_PIPE,
+ DESCRIPTOR_TYPE_SOCKET
+} descriptor_type;
+
/* One instance of this struct is created for each item in `$descriptorspec` argument to `proc_open`
* They are used within `proc_open` and freed before it returns */
typedef struct _descriptorspec_item {
int index; /* desired FD # in child process */
- int is_pipe;
+ descriptor_type type;
php_file_descriptor_t childend; /* FD # opened for use in child
* (will be copied to `index` in child) */
php_file_descriptor_t parentend; /* FD # opened for use in parent
@@ -679,7 +686,7 @@ static int set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd,
}
}
- desc->is_pipe = 1;
+ desc->type = DESCRIPTOR_TYPE_PIPE;
desc->childend = dup(*slave_fd);
desc->parentend = dup(*master_fd);
desc->mode_flags = O_RDWR;
@@ -690,6 +697,19 @@ static int set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd,
#endif
}
+/* Mark the descriptor close-on-exec, so it won't be inherited by children */
+static php_file_descriptor_t make_descriptor_cloexec(php_file_descriptor_t fd)
+{
+#ifdef PHP_WIN32
+ return dup_handle(fd, FALSE, TRUE);
+#else
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+ return fd;
+#endif
+}
+
static int set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *zmode)
{
php_file_descriptor_t newpipe[2];
@@ -699,7 +719,7 @@ static int set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *z
return FAILURE;
}
- desc->is_pipe = 1;
+ desc->type = DESCRIPTOR_TYPE_PIPE;
if (strncmp(ZSTR_VAL(zmode), "w", 1) != 0) {
desc->parentend = newpipe[1];
@@ -711,10 +731,9 @@ static int set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *z
desc->mode_flags = O_RDONLY;
}
-#ifdef PHP_WIN32
- /* don't let the child inherit the parent side of the pipe */
- desc->parentend = dup_handle(desc->parentend, FALSE, TRUE);
+ desc->parentend = make_descriptor_cloexec(desc->parentend);
+#ifdef PHP_WIN32
if (ZSTR_LEN(zmode) >= 2 && ZSTR_VAL(zmode)[1] == 'b')
desc->mode_flags |= O_BINARY;
#endif
@@ -722,6 +741,32 @@ static int set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *z
return SUCCESS;
}
+#ifdef PHP_WIN32
+#define create_socketpair(socks) socketpair_win32(AF_INET, SOCK_STREAM, 0, (socks), 0)
+#else
+#define create_socketpair(socks) socketpair(AF_UNIX, SOCK_STREAM, 0, (socks))
+#endif
+
+static int set_proc_descriptor_to_socket(descriptorspec_item *desc)
+{
+ php_socket_t sock[2];
+
+ if (create_socketpair(sock)) {
+ zend_string *err = php_socket_error_str(php_socket_errno());
+ php_error_docref(NULL, E_WARNING, "Unable to create socket pair: %s", ZSTR_VAL(err));
+ zend_string_release(err);
+ return FAILURE;
+ }
+
+ desc->type = DESCRIPTOR_TYPE_SOCKET;
+ desc->parentend = make_descriptor_cloexec((php_file_descriptor_t) sock[0]);
+
+ /* Pass sock[1] to child because it will never use overlapped IO on Windows. */
+ desc->childend = (php_file_descriptor_t) sock[1];
+
+ return SUCCESS;
+}
+
static int set_proc_descriptor_to_file(descriptorspec_item *desc, zend_string *file_path,
zend_string *file_mode)
{
@@ -827,6 +872,9 @@ static int set_proc_descriptor_from_array(zval *descitem, descriptorspec_item *d
goto finish;
}
retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode);
+ } else if (zend_string_equals_literal(ztype, "socket")) {
+ /* Set descriptor to socketpair */
+ retval = set_proc_descriptor_to_socket(&descriptors[ndesc]);
} else if (zend_string_equals_literal(ztype, "file")) {
/* Set descriptor to file */
if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) {
@@ -903,7 +951,7 @@ static int close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc
* Also, dup() the child end of all pipes as necessary so they will use the FD
* number which the user requested */
for (int i = 0; i < ndesc; i++) {
- if (descriptors[i].is_pipe) {
+ if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
close(descriptors[i].parentend);
}
if (descriptors[i].childend != descriptors[i].index) {
@@ -1194,12 +1242,13 @@ PHP_FUNCTION(proc_open)
/* Clean up all the child ends and then open streams on the parent
* ends, where appropriate */
for (i = 0; i < ndesc; i++) {
- char *mode_string = NULL;
php_stream *stream = NULL;
close_descriptor(descriptors[i].childend);
- if (descriptors[i].is_pipe) {
+ if (descriptors[i].type == DESCRIPTOR_TYPE_PIPE) {
+ char *mode_string = NULL;
+
switch (descriptors[i].mode_flags) {
#ifdef PHP_WIN32
case O_WRONLY|O_BINARY:
@@ -1219,32 +1268,31 @@ PHP_FUNCTION(proc_open)
mode_string = "r+";
break;
}
+
#ifdef PHP_WIN32
stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
descriptors[i].mode_flags), mode_string, NULL);
php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
#else
stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
-# if defined(F_SETFD) && defined(FD_CLOEXEC)
- /* Mark the descriptor close-on-exec, so it won't be inherited by
- * potential other children */
- fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
-# endif
#endif
- if (stream) {
- zval retfp;
+ } else if (descriptors[i].type == DESCRIPTOR_TYPE_SOCKET) {
+ stream = php_stream_sock_open_from_socket((php_socket_t) descriptors[i].parentend, NULL);
+ } else {
+ proc->pipes[i] = NULL;
+ }
- /* nasty hack; don't copy it */
- stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ if (stream) {
+ zval retfp;
- php_stream_to_zval(stream, &retfp);
- add_index_zval(pipes, descriptors[i].index, &retfp);
+ /* nasty hack; don't copy it */
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
- proc->pipes[i] = Z_RES(retfp);
- Z_ADDREF(retfp);
- }
- } else {
- proc->pipes[i] = NULL;
+ php_stream_to_zval(stream, &retfp);
+ add_index_zval(pipes, descriptors[i].index, &retfp);
+
+ proc->pipes[i] = Z_RES(retfp);
+ Z_ADDREF(retfp);
}
}
diff --git a/ext/standard/tests/general_functions/proc_open_sockets1.inc b/ext/standard/tests/general_functions/proc_open_sockets1.inc
new file mode 100644
index 0000000000..680f9cea5c
--- /dev/null
+++ b/ext/standard/tests/general_functions/proc_open_sockets1.inc
@@ -0,0 +1,7 @@
+<?php
+
+echo "hello";
+sleep(1);
+fwrite(STDERR, "SOME ERROR");
+sleep(1);
+echo "world";
diff --git a/ext/standard/tests/general_functions/proc_open_sockets1.phpt b/ext/standard/tests/general_functions/proc_open_sockets1.phpt
new file mode 100644
index 0000000000..baecc44827
--- /dev/null
+++ b/ext/standard/tests/general_functions/proc_open_sockets1.phpt
@@ -0,0 +1,56 @@
+--TEST--
+proc_open() with output socketpairs
+--FILE--
+<?php
+
+$cmd = [
+ getenv("TEST_PHP_EXECUTABLE"),
+ __DIR__ . '/proc_open_sockets1.inc'
+];
+
+$spec = [
+ ['null'],
+ ['socket'],
+ ['socket']
+];
+
+$proc = proc_open($cmd, $spec, $pipes);
+
+foreach ($pipes as $pipe) {
+ var_dump(stream_set_blocking($pipe, false));
+}
+
+while ($pipes) {
+ $r = $pipes;
+ $w = null;
+ $e = null;
+
+ if (!stream_select($r, $w, $e, null, 0)) {
+ throw new Error("Select failed");
+ }
+
+ foreach ($r as $i => $pipe) {
+ if (!is_resource($pipe) || feof($pipe)) {
+ unset($pipes[$i]);
+ continue;
+ }
+
+ $chunk = @fread($pipe, 8192);
+
+ if ($chunk === false) {
+ throw new Error("Failed to read: " . (error_get_last()['message'] ?? 'N/A'));
+ }
+
+ if ($chunk !== '') {
+ echo "PIPE {$i} << {$chunk}\n";
+ }
+ }
+}
+
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+PIPE 1 << hello
+PIPE 2 << SOME ERROR
+PIPE 1 << world
diff --git a/ext/standard/tests/general_functions/proc_open_sockets2.inc b/ext/standard/tests/general_functions/proc_open_sockets2.inc
new file mode 100644
index 0000000000..e9a6c9e33d
--- /dev/null
+++ b/ext/standard/tests/general_functions/proc_open_sockets2.inc
@@ -0,0 +1,7 @@
+<?php
+
+echo "hello";
+sleep(1);
+echo "world";
+
+echo strtoupper(trim(fgets(STDIN)));
diff --git a/ext/standard/tests/general_functions/proc_open_sockets2.phpt b/ext/standard/tests/general_functions/proc_open_sockets2.phpt
new file mode 100644
index 0000000000..25f3153ec4
--- /dev/null
+++ b/ext/standard/tests/general_functions/proc_open_sockets2.phpt
@@ -0,0 +1,67 @@
+--TEST--
+proc_open() with IO socketpairs
+--FILE--
+<?php
+
+function poll($pipe, $read = true)
+{
+ $r = ($read == true) ? [$pipe] : null;
+ $w = ($read == false) ? [$pipe] : null;
+ $e = null;
+
+ if (!stream_select($r, $w, $e, null, 0)) {
+ throw new \Error("Select failed");
+ }
+}
+
+function read_pipe($pipe): string
+{
+ poll($pipe);
+
+ if (false === ($chunk = @fread($pipe, 8192))) {
+ throw new Error("Failed to read: " . (error_get_last()['message'] ?? 'N/A'));
+ }
+
+ return $chunk;
+}
+
+function write_pipe($pipe, $data)
+{
+ poll($pipe, false);
+
+ if (false == @fwrite($pipe, $data)) {
+ throw new Error("Failed to write: " . (error_get_last()['message'] ?? 'N/A'));
+ }
+}
+
+$cmd = [
+ getenv("TEST_PHP_EXECUTABLE"),
+ __DIR__ . '/proc_open_sockets2.inc'
+];
+
+$spec = [
+ ['socket'],
+ ['socket']
+];
+
+$proc = proc_open($cmd, $spec, $pipes);
+
+foreach ($pipes as $pipe) {
+ var_dump(stream_set_blocking($pipe, false));
+}
+
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+
+write_pipe($pipes[0], 'done');
+fclose($pipes[0]);
+
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+STDOUT << hello
+STDOUT << world
+STDOUT << DONE
diff --git a/ext/standard/tests/general_functions/proc_open_sockets3.phpt b/ext/standard/tests/general_functions/proc_open_sockets3.phpt
new file mode 100644
index 0000000000..5ee9e53b56
--- /dev/null
+++ b/ext/standard/tests/general_functions/proc_open_sockets3.phpt
@@ -0,0 +1,55 @@
+--TEST--
+proc_open() with socket and pipe
+--FILE--
+<?php
+
+function poll($pipe, $read = true)
+{
+ $r = ($read == true) ? [$pipe] : null;
+ $w = ($read == false) ? [$pipe] : null;
+ $e = null;
+
+ if (!stream_select($r, $w, $e, null, 0)) {
+ throw new \Error("Select failed");
+ }
+}
+
+function read_pipe($pipe): string
+{
+ poll($pipe);
+
+ if (false === ($chunk = @fread($pipe, 8192))) {
+ throw new Error("Failed to read: " . (error_get_last()['message'] ?? 'N/A'));
+ }
+
+ return $chunk;
+}
+
+$cmd = [
+ getenv("TEST_PHP_EXECUTABLE"),
+ __DIR__ . '/proc_open_sockets2.inc'
+];
+
+$spec = [
+ ['pipe', 'r'],
+ ['socket']
+];
+
+$proc = proc_open($cmd, $spec, $pipes);
+
+var_dump(stream_set_blocking($pipes[1], false));
+
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+
+fwrite($pipes[0], 'done');
+fclose($pipes[0]);
+
+printf("STDOUT << %s\n", read_pipe($pipes[1]));
+
+?>
+--EXPECTF--
+bool(true)
+STDOUT << hello
+STDOUT << world
+STDOUT << DONE
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index f33c65df68..b6db92ddad 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -257,6 +257,11 @@ static void detect_is_seekable(php_stdio_stream_data *self) {
self->is_seekable = !(file_type == FILE_TYPE_PIPE || file_type == FILE_TYPE_CHAR);
self->is_pipe = file_type == FILE_TYPE_PIPE;
+
+ /* Additional check needed to distinguish between pipes and sockets. */
+ if (self->is_pipe && !GetNamedPipeInfo((HANDLE) handle, NULL, NULL, NULL, NULL)) {
+ self->is_pipe = 0;
+ }
}
#endif
}
diff --git a/win32/sockets.c b/win32/sockets.c
index c38d2bccbc..0a9a230df5 100644
--- a/win32/sockets.c
+++ b/win32/sockets.c
@@ -24,34 +24,33 @@
#include "php.h"
-PHPAPI int socketpair(int domain, int type, int protocol, SOCKET sock[2])
+PHPAPI int socketpair_win32(int domain, int type, int protocol, SOCKET sock[2], int overlapped)
{
struct sockaddr_in address;
SOCKET redirect;
int size = sizeof(address);
- if(domain != AF_INET) {
+ if (domain != AF_INET) {
WSASetLastError(WSAENOPROTOOPT);
return -1;
}
- sock[0] = sock[1] = redirect = INVALID_SOCKET;
-
+ sock[1] = redirect = INVALID_SOCKET;
- sock[0] = socket(domain, type, protocol);
+ sock[0] = socket(domain, type, protocol);
if (INVALID_SOCKET == sock[0]) {
goto error;
}
address.sin_addr.s_addr = INADDR_ANY;
- address.sin_family = AF_INET;
- address.sin_port = 0;
+ address.sin_family = AF_INET;
+ address.sin_port = 0;
- if (bind(sock[0], (struct sockaddr*)&address, sizeof(address)) != 0) {
+ if (bind(sock[0], (struct sockaddr *) &address, sizeof(address)) != 0) {
goto error;
}
- if(getsockname(sock[0], (struct sockaddr *)&address, &size) != 0) {
+ if (getsockname(sock[0], (struct sockaddr *) &address, &size) != 0) {
goto error;
}
@@ -59,17 +58,22 @@ PHPAPI int socketpair(int domain, int type, int protocol, SOCKET sock[2])
goto error;
}
- sock[1] = socket(domain, type, protocol);
+ if (overlapped) {
+ sock[1] = socket(domain, type, protocol);
+ } else {
+ sock[1] = WSASocket(domain, type, protocol, NULL, 0, 0);
+ }
+
if (INVALID_SOCKET == sock[1]) {
goto error;
}
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- if(connect(sock[1], (struct sockaddr*)&address, sizeof(address)) != 0) {
+ if (connect(sock[1], (struct sockaddr *) &address, sizeof(address)) != 0) {
goto error;
}
- redirect = accept(sock[0],(struct sockaddr*)&address, &size);
+ redirect = accept(sock[0], (struct sockaddr *) &address, &size);
if (INVALID_SOCKET == redirect) {
goto error;
}
@@ -86,3 +90,8 @@ error:
WSASetLastError(WSAECONNABORTED);
return -1;
}
+
+PHPAPI int socketpair(int domain, int type, int protocol, SOCKET sock[2])
+{
+ return socketpair_win32(domain, type, protocol, sock, 1);
+}
diff --git a/win32/sockets.h b/win32/sockets.h
index f254133cc8..2b2b5c4712 100644
--- a/win32/sockets.h
+++ b/win32/sockets.h
@@ -22,6 +22,7 @@
#ifndef PHP_WIN32_SOCKETS_H
#define PHP_WIN32_SOCKETS_H
+PHPAPI int socketpair_win32(int domain, int type, int protocol, SOCKET sock[2], int overlapped);
PHPAPI int socketpair(int domain, int type, int protocol, SOCKET sock[2]);
#endif