Skip to content

Commit e7512e6

Browse files
committed
Improve platform support by checking all available FDs as fallback
If we can not read from /dev/fd (which is available on Linux, Mac and many others), we otherwise try temporarily duplicating file descriptors in the range 0-1024 (FD_SETSIZE) to see which one is currently in use. This is known to work on more exotic platforms and also inside chroot environments without /dev/fd. Causes many syscalls, but still rather fast.
1 parent 27ccbdd commit e7512e6

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

src/Io/functions.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,24 @@
1313
*/
1414
function fds($path = '/dev/fd')
1515
{
16-
// try to get list of all open FDs (Linux/Mac and others) or simply assume range 0-1024 (FD_SETSIZE)
16+
// Try to get list of all open FDs (Linux/Mac and others)
1717
$fds = @\scandir($path);
1818

19-
return $fds !== false ? $fds : \range(0, 1024);
19+
// Otherwise try temporarily duplicating file descriptors in the range 0-1024 (FD_SETSIZE).
20+
// This is known to work on more exotic platforms and also inside chroot
21+
// environments without /dev/fd. Causes many syscalls, but still rather fast.
22+
if ($fds === false) {
23+
$fds = array();
24+
for ($i = 0; $i <= 1024; ++$i) {
25+
$copy = @\fopen('php://fd/' . $i, 'r');
26+
if ($copy !== false) {
27+
$fds[] = $i;
28+
\fclose($copy);
29+
}
30+
}
31+
}
32+
33+
return $fds;
2034
}
2135

2236
/**

tests/Io/FunctionsTest.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,29 @@ public function testFdsWithInvalidPathReturnsArray()
4141
$this->assertInternalType('array', $fds);
4242
}
4343

44+
public function testFdsWithInvalidPathReturnsSubsetOfFdsFromDevFd()
45+
{
46+
if (@scandir('/dev/fd') === false) {
47+
$this->markTestSkipped('Unable to read /dev/fd');
48+
}
49+
50+
$fds = Io\fds();
51+
$second = Io\fds('/dev/null');
52+
53+
foreach ($second as $one) {
54+
$this->assertContains($one, $fds);
55+
}
56+
}
57+
4458
public function testProcessWithoutFdsReturnsProcessWithoutClosingDefaultHandles()
4559
{
4660
$process = Io\processWithoutFds('sleep 10');
4761

4862
$this->assertInstanceOf('React\ChildProcess\Process', $process);
4963

50-
$this->assertNotContains('0>&-', $process->getCommand());
51-
$this->assertNotContains('1>&-', $process->getCommand());
52-
$this->assertNotContains('2>&-', $process->getCommand());
64+
$this->assertNotContains(' 0>&-', $process->getCommand());
65+
$this->assertNotContains(' 1>&-', $process->getCommand());
66+
$this->assertNotContains(' 2>&-', $process->getCommand());
5367
}
5468

5569
public function testProcessWithoutFdsReturnsProcessWithOriginalCommandPartOfActualCommandWhenDescriptorsNeedToBeClosed()

0 commit comments

Comments
 (0)