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