mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Support socketpairs in proc_open()
Closes GH-5777.
This commit is contained in:
parent
1a00d015be
commit
547d98b81d
10 changed files with 301 additions and 38 deletions
|
@ -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
|
||||
|
|
|
@ -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,18 +1268,20 @@ 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
|
||||
} 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;
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
zval retfp;
|
||||
|
||||
|
@ -1243,9 +1294,6 @@ PHP_FUNCTION(proc_open)
|
|||
proc->pipes[i] = Z_RES(retfp);
|
||||
Z_ADDREF(retfp);
|
||||
}
|
||||
} else {
|
||||
proc->pipes[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (1) {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
echo "hello";
|
||||
sleep(1);
|
||||
fwrite(STDERR, "SOME ERROR");
|
||||
sleep(1);
|
||||
echo "world";
|
56
ext/standard/tests/general_functions/proc_open_sockets1.phpt
Normal file
56
ext/standard/tests/general_functions/proc_open_sockets1.phpt
Normal file
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
echo "hello";
|
||||
sleep(1);
|
||||
echo "world";
|
||||
|
||||
echo strtoupper(trim(fgets(STDIN)));
|
67
ext/standard/tests/general_functions/proc_open_sockets2.phpt
Normal file
67
ext/standard/tests/general_functions/proc_open_sockets2.phpt
Normal file
|
@ -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
|
55
ext/standard/tests/general_functions/proc_open_sockets3.phpt
Normal file
55
ext/standard/tests/general_functions/proc_open_sockets3.phpt
Normal file
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -24,19 +24,18 @@
|
|||
|
||||
#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);
|
||||
if (INVALID_SOCKET == sock[0]) {
|
||||
|
@ -47,11 +46,11 @@ PHPAPI int socketpair(int domain, int type, int protocol, SOCKET sock[2])
|
|||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue