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