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 /** 92 * @return array 93 */ 94 public function __sleep() 95 { 96 throw new \BadMethodCallException('Cannot serialize '.__CLASS__); 97 } 98 99 public function __wakeup() 100 { 101 throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); 102 } 103 104 public function __destruct() 105 { 106 $this->close(); 107 } 108 109 /** 110 * {@inheritdoc} 111 */ 112 public function getDescriptors(): array 113 { 114 if (!$this->haveReadSupport) { 115 $nullstream = fopen('NUL', 'c'); 116 117 return [ 118 ['pipe', 'r'], 119 $nullstream, 120 $nullstream, 121 ]; 122 } 123 124 // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) 125 // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 126 // So we redirect output within the commandline and pass the nul device to the process 127 return [ 128 ['pipe', 'r'], 129 ['file', 'NUL', 'w'], 130 ['file', 'NUL', 'w'], 131 ]; 132 } 133 134 /** 135 * {@inheritdoc} 136 */ 137 public function getFiles(): array 138 { 139 return $this->files; 140 } 141 142 /** 143 * {@inheritdoc} 144 */ 145 public function readAndWrite(bool $blocking, bool $close = false): array 146 { 147 $this->unblock(); 148 $w = $this->write(); 149 $read = $r = $e = []; 150 151 if ($blocking) { 152 if ($w) { 153 @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); 154 } elseif ($this->fileHandles) { 155 usleep(Process::TIMEOUT_PRECISION * 1E6); 156 } 157 } 158 foreach ($this->fileHandles as $type => $fileHandle) { 159 $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); 160 161 if (isset($data[0])) { 162 $this->readBytes[$type] += \strlen($data); 163 $read[$type] = $data; 164 } 165 if ($close) { 166 ftruncate($fileHandle, 0); 167 fclose($fileHandle); 168 flock($this->lockHandles[$type], \LOCK_UN); 169 fclose($this->lockHandles[$type]); 170 unset($this->fileHandles[$type], $this->lockHandles[$type]); 171 } 172 } 173 174 return $read; 175 } 176 177 /** 178 * {@inheritdoc} 179 */ 180 public function haveReadSupport(): bool 181 { 182 return $this->haveReadSupport; 183 } 184 185 /** 186 * {@inheritdoc} 187 */ 188 public function areOpen(): bool 189 { 190 return $this->pipes && $this->fileHandles; 191 } 192 193 /** 194 * {@inheritdoc} 195 */ 196 public function close() 197 { 198 parent::close(); 199 foreach ($this->fileHandles as $type => $handle) { 200 ftruncate($handle, 0); 201 fclose($handle); 202 flock($this->lockHandles[$type], \LOCK_UN); 203 fclose($this->lockHandles[$type]); 204 } 205 $this->fileHandles = $this->lockHandles = []; 206 } 207} 208