AbstractPipes.php
1 year ago
PipesInterface.php
1 year ago
UnixPipes.php
1 year ago
WindowsPipes.php
1 year ago
WindowsPipes.php
172 lines
| 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | * This file is part of the Symfony package. |
| 5 | * |
| 6 | * (c) Fabien Potencier <fabien@symfony.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | namespace Symfony\Component\Process\Pipes; |
| 12 | |
| 13 | use Symfony\Component\Process\Exception\RuntimeException; |
| 14 | use Symfony\Component\Process\Process; |
| 15 | /** |
| 16 | * WindowsPipes implementation uses temporary files as handles. |
| 17 | * |
| 18 | * @see https://bugs.php.net/51800 |
| 19 | * @see https://bugs.php.net/65650 |
| 20 | * |
| 21 | * @author Romain Neutron <imprec@gmail.com> |
| 22 | * |
| 23 | * @internal |
| 24 | */ |
| 25 | class WindowsPipes extends \Symfony\Component\Process\Pipes\AbstractPipes |
| 26 | { |
| 27 | private $files = []; |
| 28 | private $fileHandles = []; |
| 29 | private $lockHandles = []; |
| 30 | private $readBytes = [Process::STDOUT => 0, Process::STDERR => 0]; |
| 31 | private $haveReadSupport; |
| 32 | public function __construct($input, bool $haveReadSupport) |
| 33 | { |
| 34 | $this->haveReadSupport = $haveReadSupport; |
| 35 | if ($this->haveReadSupport) { |
| 36 | // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. |
| 37 | // Workaround for this problem is to use temporary files instead of pipes on Windows platform. |
| 38 | // |
| 39 | // @see https://bugs.php.net/51800 |
| 40 | $pipes = [Process::STDOUT => Process::OUT, Process::STDERR => Process::ERR]; |
| 41 | $tmpDir = sys_get_temp_dir(); |
| 42 | $lastError = 'unknown reason'; |
| 43 | set_error_handler(function ($type, $msg) use(&$lastError) { |
| 44 | $lastError = $msg; |
| 45 | }); |
| 46 | for ($i = 0;; ++$i) { |
| 47 | foreach ($pipes as $pipe => $name) { |
| 48 | $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); |
| 49 | if (!($h = fopen($file . '.lock', 'w'))) { |
| 50 | if (file_exists($file . '.lock')) { |
| 51 | continue 2; |
| 52 | } |
| 53 | restore_error_handler(); |
| 54 | throw new RuntimeException('A temporary file could not be opened to write the process output: ' . $lastError); |
| 55 | } |
| 56 | if (!flock($h, \LOCK_EX | \LOCK_NB)) { |
| 57 | continue 2; |
| 58 | } |
| 59 | if (isset($this->lockHandles[$pipe])) { |
| 60 | flock($this->lockHandles[$pipe], \LOCK_UN); |
| 61 | fclose($this->lockHandles[$pipe]); |
| 62 | } |
| 63 | $this->lockHandles[$pipe] = $h; |
| 64 | if (!($h = fopen($file, 'w')) || !fclose($h) || !($h = fopen($file, 'r'))) { |
| 65 | flock($this->lockHandles[$pipe], \LOCK_UN); |
| 66 | fclose($this->lockHandles[$pipe]); |
| 67 | unset($this->lockHandles[$pipe]); |
| 68 | continue 2; |
| 69 | } |
| 70 | $this->fileHandles[$pipe] = $h; |
| 71 | $this->files[$pipe] = $file; |
| 72 | } |
| 73 | break; |
| 74 | } |
| 75 | restore_error_handler(); |
| 76 | } |
| 77 | parent::__construct($input); |
| 78 | } |
| 79 | public function __sleep() : array |
| 80 | { |
| 81 | throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); |
| 82 | } |
| 83 | public function __wakeup() |
| 84 | { |
| 85 | throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); |
| 86 | } |
| 87 | public function __destruct() |
| 88 | { |
| 89 | $this->close(); |
| 90 | } |
| 91 | /** |
| 92 | * {@inheritdoc} |
| 93 | */ |
| 94 | public function getDescriptors() : array |
| 95 | { |
| 96 | if (!$this->haveReadSupport) { |
| 97 | $nullstream = fopen('NUL', 'c'); |
| 98 | return [['pipe', 'r'], $nullstream, $nullstream]; |
| 99 | } |
| 100 | // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) |
| 101 | // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 |
| 102 | // So we redirect output within the commandline and pass the nul device to the process |
| 103 | return [['pipe', 'r'], ['file', 'NUL', 'w'], ['file', 'NUL', 'w']]; |
| 104 | } |
| 105 | /** |
| 106 | * {@inheritdoc} |
| 107 | */ |
| 108 | public function getFiles() : array |
| 109 | { |
| 110 | return $this->files; |
| 111 | } |
| 112 | /** |
| 113 | * {@inheritdoc} |
| 114 | */ |
| 115 | public function readAndWrite(bool $blocking, bool $close = \false) : array |
| 116 | { |
| 117 | $this->unblock(); |
| 118 | $w = $this->write(); |
| 119 | $read = $r = $e = []; |
| 120 | if ($blocking) { |
| 121 | if ($w) { |
| 122 | @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1000000.0); |
| 123 | } elseif ($this->fileHandles) { |
| 124 | usleep((int) (Process::TIMEOUT_PRECISION * 1000000.0)); |
| 125 | } |
| 126 | } |
| 127 | foreach ($this->fileHandles as $type => $fileHandle) { |
| 128 | $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); |
| 129 | if (isset($data[0])) { |
| 130 | $this->readBytes[$type] += \strlen($data); |
| 131 | $read[$type] = $data; |
| 132 | } |
| 133 | if ($close) { |
| 134 | ftruncate($fileHandle, 0); |
| 135 | fclose($fileHandle); |
| 136 | flock($this->lockHandles[$type], \LOCK_UN); |
| 137 | fclose($this->lockHandles[$type]); |
| 138 | unset($this->fileHandles[$type], $this->lockHandles[$type]); |
| 139 | } |
| 140 | } |
| 141 | return $read; |
| 142 | } |
| 143 | /** |
| 144 | * {@inheritdoc} |
| 145 | */ |
| 146 | public function haveReadSupport() : bool |
| 147 | { |
| 148 | return $this->haveReadSupport; |
| 149 | } |
| 150 | /** |
| 151 | * {@inheritdoc} |
| 152 | */ |
| 153 | public function areOpen() : bool |
| 154 | { |
| 155 | return $this->pipes && $this->fileHandles; |
| 156 | } |
| 157 | /** |
| 158 | * {@inheritdoc} |
| 159 | */ |
| 160 | public function close() |
| 161 | { |
| 162 | parent::close(); |
| 163 | foreach ($this->fileHandles as $type => $handle) { |
| 164 | ftruncate($handle, 0); |
| 165 | fclose($handle); |
| 166 | flock($this->lockHandles[$type], \LOCK_UN); |
| 167 | fclose($this->lockHandles[$type]); |
| 168 | } |
| 169 | $this->fileHandles = $this->lockHandles = []; |
| 170 | } |
| 171 | } |
| 172 |