Skip to content

Commit 2da0bdf

Browse files
committed
Internal refactoring to reuse file descriptor close logic
1 parent 7ac2c24 commit 2da0bdf

File tree

5 files changed

+122
-43
lines changed

5 files changed

+122
-43
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
}
1212
],
1313
"autoload": {
14-
"psr-4": { "Clue\\React\\SshProxy\\": "src/" }
14+
"psr-4": { "Clue\\React\\SshProxy\\": "src/" },
15+
"files": [ "src/Io/functions.php" ]
1516
},
1617
"require": {
1718
"php": ">=5.3",

src/Io/functions.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Clue\React\SshProxy\Io;
4+
5+
use React\ChildProcess\Process;
6+
7+
/**
8+
* Returns a list of active file descriptors (may contain bogus entries)
9+
*
10+
* @param string $path
11+
* @return array
12+
* @internal
13+
*/
14+
function fds($path = '/proc/self/fd')
15+
{
16+
// try to get list of all open FDs (Linux only) or simply assume range 0-1024 (FD_SETSIZE)
17+
$fds = @\scandir($path);
18+
19+
return $fds !== false ? $fds : \range(0, 1024);
20+
}
21+
22+
/**
23+
* Creates a Process with the given command modified in such a way that any additional FDs are explicitly not passed along
24+
*
25+
* @param string $command
26+
* @return Process
27+
* @internal
28+
*/
29+
function processWithoutFds($command)
30+
{
31+
// try to get list of all open FDs
32+
$fds = fds();
33+
34+
// do not inherit open FDs by explicitly closing all of them
35+
foreach ($fds as $fd) {
36+
if ($fd > 2) {
37+
$command .= ' ' . $fd . '>&-';
38+
}
39+
}
40+
41+
// default `sh` only accepts single-digit FDs, so run in bash if needed
42+
if ($fds && max($fds) > 9) {
43+
$command = 'exec bash -c ' . escapeshellarg($command);
44+
}
45+
46+
return new Process($command);
47+
}

src/SshProcessConnector.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Clue\React\SshProxy\Io\CompositeConnection;
66
use Clue\React\SshProxy\Io\LineSeparatedReader;
77
use React\EventLoop\LoopInterface;
8-
use React\ChildProcess\Process;
98
use React\Promise\Deferred;
109
use React\Socket\ConnectorInterface;
1110

@@ -67,26 +66,7 @@ public function connect($uri)
6766
}
6867

6968
$command = $this->cmd . ' -W ' . \escapeshellarg($parts['host'] . ':' . $parts['port']);
70-
71-
// try to get list of all open FDs (Linux only) or simply assume range 3-1024 (FD_SETSIZE)
72-
$fds = @scandir('/proc/self/fd');
73-
if ($fds === false) {
74-
$fds = range(3, 1024); // @codeCoverageIgnore
75-
}
76-
77-
// do not inherit open FDs by explicitly closing all of them
78-
foreach ($fds as $fd) {
79-
if ($fd > 2) {
80-
$command .= ' ' . $fd . '>&-';
81-
}
82-
}
83-
84-
// default `sh` only accepts single-digit FDs, so run in bash if needed
85-
if ($fds && max($fds) > 9) {
86-
$command = 'exec bash -c ' . escapeshellarg($command);
87-
}
88-
89-
$process = new Process($command);
69+
$process = Io\processWithoutFds($command);
9070
$process->start($this->loop);
9171

9272
$deferred = new Deferred(function () use ($process, $uri) {

src/SshSocksConnector.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -195,27 +195,7 @@ public function idle()
195195

196196
private function listen()
197197
{
198-
$command = $this->cmd;
199-
200-
// try to get list of all open FDs (Linux only) or simply assume range 3-1024 (FD_SETSIZE)
201-
$fds = @scandir('/proc/self/fd');
202-
if ($fds === false) {
203-
$fds = range(3, 1024); // @codeCoverageIgnore
204-
}
205-
206-
// do not inherit open FDs by explicitly closing all of them
207-
foreach ($fds as $fd) {
208-
if ($fd > 2) {
209-
$command .= ' ' . $fd . '>&-';
210-
}
211-
}
212-
213-
// default `sh` only accepts single-digit FDs, so run in bash if needed
214-
if ($fds && max($fds) > 9) {
215-
$command = 'exec bash -c ' . escapeshellarg($command);
216-
}
217-
218-
$process = new Process($command);
198+
$process = Io\processWithoutFds($this->cmd);
219199
$process->start($this->loop);
220200

221201
$deferred = new Deferred(function () use ($process) {

tests/Io/FunctionsTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
use Clue\React\SshProxy\Io;
4+
use PHPUnit\Framework\TestCase;
5+
6+
class FunctionsTest extends TestCase
7+
{
8+
public function testFdsReturnsArray()
9+
{
10+
$fds = Io\fds();
11+
12+
$this->assertInternalType('array', $fds);
13+
}
14+
15+
public function testFdsReturnsArrayWithStdioHandles()
16+
{
17+
// skip when running with closed handles: vendor/bin/phpunit 0<&-
18+
if (!defined('STDIN') || !defined('STDOUT') || !defined('STDERR') || !@fstat(STDIN) || !@fstat(STDOUT) || !@fstat(STDERR)) {
19+
$this->markTestSkipped('Test suite does not appear to run with standard I/O handles');
20+
}
21+
22+
$fds = Io\fds();
23+
24+
$this->assertContains(0, $fds);
25+
$this->assertContains(1, $fds);
26+
$this->assertContains(2, $fds);
27+
}
28+
29+
public function testFdsReturnsSameArrayTwice()
30+
{
31+
$fds = Io\fds();
32+
$second = Io\fds();
33+
34+
$this->assertEquals($fds, $second);
35+
}
36+
37+
public function testFdsWithInvalidPathReturnsArray()
38+
{
39+
$fds = Io\fds('/dev/null');
40+
41+
$this->assertInternalType('array', $fds);
42+
}
43+
44+
public function testProcessWithoutFdsReturnsProcessWithoutClosingDefaultHandles()
45+
{
46+
$process = Io\processWithoutFds('sleep 10');
47+
48+
$this->assertInstanceOf('React\ChildProcess\Process', $process);
49+
50+
$this->assertNotContains('0>&-', $process->getCommand());
51+
$this->assertNotContains('1>&-', $process->getCommand());
52+
$this->assertNotContains('2>&-', $process->getCommand());
53+
}
54+
55+
public function testProcessWithoutFdsReturnsProcessWithOriginalCommandPartOfActualCommandWhenDescriptorsNeedToBeClosed()
56+
{
57+
// skip when running with closed handles: vendor/bin/phpunit 0<&-
58+
// bypass for example with dummy handles: vendor/bin/phpunit 8<&-
59+
$fds = Io\fds();
60+
if (!$fds || max($fds) < 3) {
61+
$this->markTestSkipped('Did not detect additional file descriptors to be closed');
62+
}
63+
64+
$process = Io\processWithoutFds('sleep 10');
65+
66+
$this->assertInstanceOf('React\ChildProcess\Process', $process);
67+
68+
$this->assertNotEquals('sleep 10', $process->getCommand());
69+
$this->assertContains('sleep 10', $process->getCommand());
70+
}
71+
}

0 commit comments

Comments
 (0)