Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Process\Pipes; Chris@0: Chris@17: use Symfony\Component\Process\Exception\RuntimeException; Chris@0: use Symfony\Component\Process\Process; Chris@0: Chris@0: /** Chris@0: * WindowsPipes implementation uses temporary files as handles. Chris@0: * Chris@0: * @see https://bugs.php.net/bug.php?id=51800 Chris@0: * @see https://bugs.php.net/bug.php?id=65650 Chris@0: * Chris@0: * @author Romain Neutron Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: class WindowsPipes extends AbstractPipes Chris@0: { Chris@17: private $files = []; Chris@17: private $fileHandles = []; Chris@17: private $lockHandles = []; Chris@17: private $readBytes = [ Chris@0: Process::STDOUT => 0, Chris@0: Process::STDERR => 0, Chris@17: ]; Chris@0: private $haveReadSupport; Chris@0: Chris@0: public function __construct($input, $haveReadSupport) Chris@0: { Chris@0: $this->haveReadSupport = (bool) $haveReadSupport; Chris@0: Chris@0: if ($this->haveReadSupport) { Chris@0: // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. Chris@0: // Workaround for this problem is to use temporary files instead of pipes on Windows platform. Chris@0: // Chris@0: // @see https://bugs.php.net/bug.php?id=51800 Chris@17: $pipes = [ Chris@0: Process::STDOUT => Process::OUT, Chris@0: Process::STDERR => Process::ERR, Chris@17: ]; Chris@0: $tmpDir = sys_get_temp_dir(); Chris@0: $lastError = 'unknown reason'; Chris@0: set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); Chris@0: for ($i = 0;; ++$i) { Chris@0: foreach ($pipes as $pipe => $name) { Chris@0: $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); Chris@17: Chris@17: if (!$h = fopen($file.'.lock', 'w')) { Chris@17: restore_error_handler(); Chris@17: throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError)); Chris@17: } Chris@17: if (!flock($h, LOCK_EX | LOCK_NB)) { Chris@0: continue 2; Chris@0: } Chris@17: if (isset($this->lockHandles[$pipe])) { Chris@17: flock($this->lockHandles[$pipe], LOCK_UN); Chris@17: fclose($this->lockHandles[$pipe]); Chris@0: } Chris@17: $this->lockHandles[$pipe] = $h; Chris@17: Chris@17: if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) { Chris@17: flock($this->lockHandles[$pipe], LOCK_UN); Chris@17: fclose($this->lockHandles[$pipe]); Chris@17: unset($this->lockHandles[$pipe]); Chris@0: continue 2; Chris@0: } Chris@17: $this->fileHandles[$pipe] = $h; Chris@0: $this->files[$pipe] = $file; Chris@0: } Chris@0: break; Chris@0: } Chris@0: restore_error_handler(); Chris@0: } Chris@0: Chris@0: parent::__construct($input); Chris@0: } Chris@0: Chris@0: public function __destruct() Chris@0: { Chris@0: $this->close(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDescriptors() Chris@0: { Chris@0: if (!$this->haveReadSupport) { Chris@0: $nullstream = fopen('NUL', 'c'); Chris@0: Chris@17: return [ Chris@17: ['pipe', 'r'], Chris@0: $nullstream, Chris@0: $nullstream, Chris@17: ]; Chris@0: } Chris@0: Chris@0: // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) Chris@0: // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 Chris@0: // So we redirect output within the commandline and pass the nul device to the process Chris@17: return [ Chris@17: ['pipe', 'r'], Chris@17: ['file', 'NUL', 'w'], Chris@17: ['file', 'NUL', 'w'], Chris@17: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getFiles() Chris@0: { Chris@0: return $this->files; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function readAndWrite($blocking, $close = false) Chris@0: { Chris@0: $this->unblock(); Chris@0: $w = $this->write(); Chris@17: $read = $r = $e = []; Chris@0: Chris@0: if ($blocking) { Chris@0: if ($w) { Chris@0: @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); Chris@0: } elseif ($this->fileHandles) { Chris@0: usleep(Process::TIMEOUT_PRECISION * 1E6); Chris@0: } Chris@0: } Chris@0: foreach ($this->fileHandles as $type => $fileHandle) { Chris@0: $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); Chris@0: Chris@0: if (isset($data[0])) { Chris@17: $this->readBytes[$type] += \strlen($data); Chris@0: $read[$type] = $data; Chris@0: } Chris@0: if ($close) { Chris@17: ftruncate($fileHandle, 0); Chris@0: fclose($fileHandle); Chris@17: flock($this->lockHandles[$type], LOCK_UN); Chris@17: fclose($this->lockHandles[$type]); Chris@17: unset($this->fileHandles[$type], $this->lockHandles[$type]); Chris@0: } Chris@0: } Chris@0: Chris@0: return $read; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function haveReadSupport() Chris@0: { Chris@0: return $this->haveReadSupport; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function areOpen() Chris@0: { Chris@0: return $this->pipes && $this->fileHandles; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function close() Chris@0: { Chris@0: parent::close(); Chris@17: foreach ($this->fileHandles as $type => $handle) { Chris@17: ftruncate($handle, 0); Chris@0: fclose($handle); Chris@17: flock($this->lockHandles[$type], LOCK_UN); Chris@17: fclose($this->lockHandles[$type]); Chris@0: } Chris@17: $this->fileHandles = $this->lockHandles = []; Chris@0: } Chris@0: }