-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from clue-labs/fds
Improve platform support (chroot environments, Mac and others) and do not inherit open FDs to SSH child process by overwriting and closing
- Loading branch information
Showing
7 changed files
with
168 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php | ||
|
||
namespace Clue\React\SshProxy\Io; | ||
|
||
use React\ChildProcess\Process; | ||
|
||
/** | ||
* Returns a list of active file descriptors (may contain bogus entries) | ||
* | ||
* @param string $path | ||
* @return array | ||
* @internal | ||
*/ | ||
function fds($path = '/dev/fd') | ||
{ | ||
// Try to get list of all open FDs (Linux/Mac and others) | ||
$fds = @\scandir($path); | ||
|
||
// Otherwise try temporarily duplicating file descriptors in the range 0-1024 (FD_SETSIZE). | ||
// This is known to work on more exotic platforms and also inside chroot | ||
// environments without /dev/fd. Causes many syscalls, but still rather fast. | ||
if ($fds === false) { | ||
$fds = array(); | ||
for ($i = 0; $i <= 1024; ++$i) { | ||
$copy = @\fopen('php://fd/' . $i, 'r'); | ||
if ($copy !== false) { | ||
$fds[] = $i; | ||
\fclose($copy); | ||
} | ||
} | ||
} | ||
|
||
return $fds; | ||
} | ||
|
||
/** | ||
* Creates a Process with the given command modified in such a way that any additional FDs are explicitly not passed along | ||
* | ||
* @param string $command | ||
* @return Process | ||
* @internal | ||
*/ | ||
function processWithoutFds($command) | ||
{ | ||
// launch process with default STDIO pipes | ||
$pipes = array( | ||
array('pipe', 'r'), | ||
array('pipe', 'w'), | ||
array('pipe', 'w') | ||
); | ||
|
||
// try to get list of all open FDs | ||
$fds = fds(); | ||
|
||
// do not inherit open FDs by explicitly overwriting existing FDs with dummy files | ||
// additionally, close all dummy files in the child process again | ||
foreach ($fds as $fd) { | ||
if ($fd > 2) { | ||
$pipes[$fd] = array('file', '/dev/null', 'r'); | ||
$command .= ' ' . $fd . '>&-'; | ||
} | ||
} | ||
|
||
// default `sh` only accepts single-digit FDs, so run in bash if needed | ||
if ($fds && max($fds) > 9) { | ||
$command = 'exec bash -c ' . escapeshellarg($command); | ||
} | ||
|
||
return new Process($command, null, null, $pipes); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
use Clue\React\SshProxy\Io; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class FunctionsTest extends TestCase | ||
{ | ||
public function testFdsReturnsArray() | ||
{ | ||
$fds = Io\fds(); | ||
|
||
$this->assertInternalType('array', $fds); | ||
} | ||
|
||
public function testFdsReturnsArrayWithStdioHandles() | ||
{ | ||
// skip when running with closed handles: vendor/bin/phpunit 0<&- | ||
if (!defined('STDIN') || !defined('STDOUT') || !defined('STDERR') || !@fstat(STDIN) || !@fstat(STDOUT) || !@fstat(STDERR)) { | ||
$this->markTestSkipped('Test suite does not appear to run with standard I/O handles'); | ||
} | ||
|
||
$fds = Io\fds(); | ||
|
||
$this->assertContains(0, $fds); | ||
$this->assertContains(1, $fds); | ||
$this->assertContains(2, $fds); | ||
} | ||
|
||
public function testFdsReturnsSameArrayTwice() | ||
{ | ||
$fds = Io\fds(); | ||
$second = Io\fds(); | ||
|
||
$this->assertEquals($fds, $second); | ||
} | ||
|
||
public function testFdsWithInvalidPathReturnsArray() | ||
{ | ||
$fds = Io\fds('/dev/null'); | ||
|
||
$this->assertInternalType('array', $fds); | ||
} | ||
|
||
public function testFdsWithInvalidPathReturnsSubsetOfFdsFromDevFd() | ||
{ | ||
if (@scandir('/dev/fd') === false) { | ||
$this->markTestSkipped('Unable to read /dev/fd'); | ||
} | ||
|
||
$fds = Io\fds(); | ||
$second = Io\fds('/dev/null'); | ||
|
||
foreach ($second as $one) { | ||
$this->assertContains($one, $fds); | ||
} | ||
} | ||
|
||
public function testProcessWithoutFdsReturnsProcessWithoutClosingDefaultHandles() | ||
{ | ||
$process = Io\processWithoutFds('sleep 10'); | ||
|
||
$this->assertInstanceOf('React\ChildProcess\Process', $process); | ||
|
||
$this->assertNotContains(' 0>&-', $process->getCommand()); | ||
$this->assertNotContains(' 1>&-', $process->getCommand()); | ||
$this->assertNotContains(' 2>&-', $process->getCommand()); | ||
} | ||
|
||
public function testProcessWithoutFdsReturnsProcessWithOriginalCommandPartOfActualCommandWhenDescriptorsNeedToBeClosed() | ||
{ | ||
// skip when running with closed handles: vendor/bin/phpunit 0<&- | ||
// bypass for example with dummy handles: vendor/bin/phpunit 8<&- | ||
$fds = Io\fds(); | ||
if (!$fds || max($fds) < 3) { | ||
$this->markTestSkipped('Did not detect additional file descriptors to be closed'); | ||
} | ||
|
||
$process = Io\processWithoutFds('sleep 10'); | ||
|
||
$this->assertInstanceOf('React\ChildProcess\Process', $process); | ||
|
||
$this->assertNotEquals('sleep 10', $process->getCommand()); | ||
$this->assertContains('sleep 10', $process->getCommand()); | ||
} | ||
} |