Skip to content

Commit 526337b

Browse files
committed
Add socketpair() support to proc_open().
1 parent 562ceae commit 526337b

File tree

5 files changed

+247
-47
lines changed

5 files changed

+247
-47
lines changed

ext/standard/proc_open.c

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ static inline HANDLE dup_fd_as_handle(int fd)
451451
* They are used within `proc_open` and freed before it returns */
452452
typedef struct _descriptorspec_item {
453453
int index; /* desired FD # in child process */
454-
int is_pipe;
454+
int is_pipe; /* =1 for pipe / tty, =2 for socketpair */
455455
php_file_descriptor_t childend; /* FD # opened for use in child
456456
* (will be copied to `index` in child) */
457457
php_file_descriptor_t parentend; /* FD # opened for use in parent
@@ -725,6 +725,40 @@ static int set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *z
725725
return SUCCESS;
726726
}
727727

728+
static int set_proc_descriptor_to_socket(descriptorspec_item *desc)
729+
{
730+
php_socket_t sock[2];
731+
732+
#ifdef PHP_WIN32
733+
if (socketpair(AF_INET, SOCK_STREAM, 0, sock)) {
734+
#else
735+
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock)) {
736+
#endif
737+
zend_string *err = php_socket_error_str(php_socket_errno());
738+
php_error_docref(NULL, E_WARNING, "Unable to create socket pair %s", ZSTR_VAL(err));
739+
zend_string_release(err);
740+
return FAILURE;
741+
}
742+
743+
/* Pass sock[1] to child because it will not use overlapped IO on Windows (even when AcceptEx() is not available). */
744+
desc->childend = (php_file_descriptor_t) sock[1];
745+
desc->is_pipe = 2;
746+
747+
#ifdef PHP_WIN32
748+
/* Don't let the child inherit the parent socket */
749+
desc->parentend = dup_handle((php_file_descriptor_t) sock[0], FALSE, TRUE);
750+
#else
751+
desc->parentend = sock[0];
752+
753+
#if defined(F_SETFD) && defined(FD_CLOEXEC)
754+
/* Don't let the child inherit the parent socket */
755+
fcntl(desc->parentend, F_SETFD, FD_CLOEXEC);
756+
#endif
757+
#endif
758+
759+
return SUCCESS;
760+
}
761+
728762
static int set_proc_descriptor_to_file(descriptorspec_item *desc, zend_string *file_path,
729763
zend_string *file_mode)
730764
{
@@ -830,6 +864,9 @@ static int set_proc_descriptor_from_array(zval *descitem, descriptorspec_item *d
830864
goto finish;
831865
}
832866
retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode);
867+
} else if (zend_string_equals_literal(ztype, "socket")) {
868+
/* Set descriptor to socketpair */
869+
retval = set_proc_descriptor_to_socket(&descriptors[ndesc]);
833870
} else if (zend_string_equals_literal(ztype, "file")) {
834871
/* Set descriptor to file */
835872
if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) {
@@ -907,15 +944,24 @@ static int close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc
907944
* number which the user requested */
908945
for (int i = 0; i < ndesc; i++) {
909946
if (descriptors[i].is_pipe) {
910-
close(descriptors[i].parentend);
947+
if (descriptors[i].is_pipe == 2) {
948+
closesocket(descriptors[i].parentend);
949+
} else {
950+
close(descriptors[i].parentend);
951+
}
911952
}
912953
if (descriptors[i].childend != descriptors[i].index) {
913954
if (dup2(descriptors[i].childend, descriptors[i].index) < 0) {
914955
php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into " \
915956
"file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(errno));
916957
return FAILURE;
917958
}
918-
close(descriptors[i].childend);
959+
960+
if (descriptors[i].is_pipe == 2) {
961+
closesocket(descriptors[i].childend);
962+
} else {
963+
close(descriptors[i].childend);
964+
}
919965
}
920966
}
921967

@@ -1204,37 +1250,44 @@ PHP_FUNCTION(proc_open)
12041250
close_descriptor(descriptors[i].childend);
12051251

12061252
if (descriptors[i].is_pipe) {
1207-
switch (descriptors[i].mode_flags) {
1253+
if (descriptors[i].is_pipe == 1) {
1254+
switch (descriptors[i].mode_flags) {
12081255
#ifdef PHP_WIN32
1209-
case O_WRONLY|O_BINARY:
1210-
mode_string = "wb";
1211-
break;
1212-
case O_RDONLY|O_BINARY:
1213-
mode_string = "rb";
1214-
break;
1256+
case O_WRONLY|O_BINARY:
1257+
mode_string = "wb";
1258+
break;
1259+
case O_RDONLY|O_BINARY:
1260+
mode_string = "rb";
1261+
break;
12151262
#endif
1216-
case O_WRONLY:
1217-
mode_string = "w";
1218-
break;
1219-
case O_RDONLY:
1220-
mode_string = "r";
1221-
break;
1222-
case O_RDWR:
1223-
mode_string = "r+";
1224-
break;
1263+
case O_WRONLY:
1264+
mode_string = "w";
1265+
break;
1266+
case O_RDONLY:
1267+
mode_string = "r";
1268+
break;
1269+
case O_RDWR:
1270+
mode_string = "r+";
1271+
break;
1272+
}
12251273
}
1274+
1275+
if (descriptors[i].is_pipe == 2) {
1276+
stream = _php_stream_sock_open_from_socket((php_socket_t) descriptors[i].parentend, NULL STREAMS_CC);
1277+
} else {
12261278
#ifdef PHP_WIN32
1227-
stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
1228-
descriptors[i].mode_flags), mode_string, NULL);
1229-
php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
1279+
stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
1280+
descriptors[i].mode_flags), mode_string, NULL);
1281+
php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
12301282
#else
1231-
stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1283+
stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
12321284
# if defined(F_SETFD) && defined(FD_CLOEXEC)
1233-
/* Mark the descriptor close-on-exec, so it won't be inherited by
1234-
* potential other children */
1235-
fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
1285+
/* Mark the descriptor close-on-exec, so it won't be inherited by
1286+
* potential other children */
1287+
fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
12361288
# endif
12371289
#endif
1290+
}
12381291
if (stream) {
12391292
zval retfp;
12401293

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
echo "hello";
4+
sleep(1);
5+
fwrite(STDERR, "SOME ERROR");
6+
sleep(1);
7+
echo "world";
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
proc_open() with socketpairs
3+
--FILE--
4+
<?php
5+
6+
$cmd = [
7+
getenv("TEST_PHP_EXECUTABLE"),
8+
__DIR__ . '/proc_open_sockets1.inc'
9+
];
10+
11+
$spec = [
12+
['null'],
13+
['socket'],
14+
['socket']
15+
];
16+
17+
$proc = proc_open($cmd, $spec, $pipes);
18+
19+
foreach ($pipes as $pipe) {
20+
var_dump(stream_set_blocking($pipe, false));
21+
}
22+
23+
while ($pipes) {
24+
$r = $pipes;
25+
$w = null;
26+
$e = null;
27+
28+
if (!stream_select($r, $w, $e, null, 0)) {
29+
throw new \Error("Select failed");
30+
}
31+
32+
foreach ($r as $i => $pipe) {
33+
if (!is_resource($pipe) || feof($pipe)) {
34+
unset($pipes[$i]);
35+
continue;
36+
}
37+
38+
$chunk = @fread($pipe, 8192);
39+
40+
if ($chunk === false) {
41+
throw new \Error("Failed to read: " . (error_get_last()['message'] ?? 'N/A'));
42+
}
43+
44+
if ($chunk !== '') {
45+
echo "PIPE {$i} << {$chunk}\n";
46+
}
47+
}
48+
}
49+
50+
?>
51+
--EXPECTF--
52+
bool(true)
53+
bool(true)
54+
PIPE 1 << hello
55+
PIPE 2 << SOME ERROR
56+
PIPE 1 << world

main/php_network.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ typedef int php_socket_t;
112112
#define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4)
113113
#define STREAM_SOCKOP_TCP_NODELAY (1 << 5)
114114

115+
#ifdef PHP_WIN32
116+
#define SOCK_OVERLAPPED (WSA_FLAG_OVERLAPPED << 8)
117+
#define SOCK_CLOEXEC (WSA_FLAG_NO_HANDLE_INHERIT << 8)
118+
#else
119+
#define SOCK_OVERLAPPED 0
120+
#ifndef SOCK_CLOEXEC
121+
#define SOCK_CLOEXEC 0
122+
#endif
123+
#endif
115124

116125
/* uncomment this to debug poll(2) emulation on systems that have poll(2) */
117126
/* #define PHP_USE_POLL_2_EMULATION 1 */

0 commit comments

Comments
 (0)