|
2 | 2 |
|
3 | 3 | namespace DivineOmega\SSHConnection; |
4 | 4 |
|
| 5 | +use RuntimeException; |
| 6 | + |
5 | 7 | class SSHCommand |
6 | 8 | { |
7 | | - public $output = null; |
8 | | - public $error = null; |
| 9 | + const EXECUTION_TIMEOUT_SECONDS = 30; |
| 10 | + const STREAM_BYTES_PER_READ = 4096; |
| 11 | + |
| 12 | + private $resource; |
| 13 | + private $command; |
| 14 | + private $output; |
| 15 | + private $error; |
9 | 16 |
|
10 | 17 | public function __construct($resource, string $command) |
11 | 18 | { |
12 | | - $stdout = ssh2_exec($resource, $command); |
| 19 | + $this->resource = $resource; |
| 20 | + $this->command = $command; |
| 21 | + |
| 22 | + $this->execute(); |
| 23 | + } |
| 24 | + |
| 25 | + private function execute() |
| 26 | + { |
| 27 | + $stdout = ssh2_exec($this->resource, $this->command); |
| 28 | + |
| 29 | + if (!$stdout) { |
| 30 | + throw new RuntimeException('Failed to execute command (no stdout stream): '.$this->command); |
| 31 | + } |
| 32 | + |
13 | 33 | $stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR); |
14 | 34 |
|
15 | | - if (empty($stdout)) { |
16 | | - throw new \RuntimeException('Failed to execute command: '.$command); |
| 35 | + if (!$stderr) { |
| 36 | + throw new RuntimeException('Failed to execute command (no stdout stream): '.$this->command); |
17 | 37 | } |
18 | 38 |
|
19 | 39 | $startTime = time(); |
20 | 40 |
|
21 | | - // Try for 30s |
22 | 41 | do { |
23 | | - $this->error = fread($stderr, 4096); |
24 | | - $this->output = fread($stdout, 4096); |
25 | | - $done = 0; |
26 | | - |
27 | | - if (feof($stderr)) { |
28 | | - $done++; |
29 | | - } |
30 | | - |
31 | | - if (feof($stdout)) { |
32 | | - $done++; |
33 | | - } |
| 42 | + $this->error = fread($stderr, self::STREAM_BYTES_PER_READ); |
| 43 | + $this->output = fread($stdout, self::STREAM_BYTES_PER_READ); |
34 | 44 |
|
35 | | - $span = time() - $startTime; |
| 45 | + $streamsComplete = (feof($stderr) && feof($stdout)); |
36 | 46 |
|
37 | | - if ($done < 2) { |
| 47 | + if (!$streamsComplete) { |
| 48 | + // Prevent thrashing. |
38 | 49 | sleep(1); |
39 | 50 | } |
40 | 51 |
|
41 | | - } while (($span < 30) && ($done < 2)); |
| 52 | + $executionDuration = time() - $startTime; |
42 | 53 |
|
| 54 | + } while ($executionDuration <= self::EXECUTION_TIMEOUT_SECONDS && !$streamsComplete); |
43 | 55 | } |
44 | 56 |
|
45 | | - /** |
46 | | - * @return bool|string|null |
47 | | - */ |
48 | | - public function getOutput() |
| 57 | + public function getOutput(): string |
49 | 58 | { |
50 | 59 | return $this->output; |
51 | 60 | } |
52 | 61 |
|
53 | | - /** |
54 | | - * @return bool|string|null |
55 | | - */ |
56 | | - public function getError() |
| 62 | + public function getError(): string |
57 | 63 | { |
58 | 64 | return $this->error; |
59 | 65 | } |
|
0 commit comments