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@0: use Symfony\Component\Process\Process; Chris@0: use Symfony\Component\Process\Exception\RuntimeException; 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@0: /** @var array */ Chris@0: private $files = array(); Chris@0: /** @var array */ Chris@0: private $fileHandles = array(); Chris@0: /** @var array */ Chris@0: private $readBytes = array( Chris@0: Process::STDOUT => 0, Chris@0: Process::STDERR => 0, Chris@0: ); Chris@0: /** @var bool */ 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@0: $pipes = array( Chris@0: Process::STDOUT => Process::OUT, Chris@0: Process::STDERR => Process::ERR, Chris@0: ); Chris@0: $tmpCheck = false; 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@0: if (file_exists($file) && !unlink($file)) { Chris@0: continue 2; Chris@0: } Chris@0: $h = fopen($file, 'xb'); Chris@0: if (!$h) { Chris@0: $error = $lastError; Chris@0: if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) { Chris@0: continue; Chris@0: } Chris@0: restore_error_handler(); Chris@0: throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error)); Chris@0: } Chris@0: if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) { Chris@0: continue 2; Chris@0: } Chris@0: if (isset($this->files[$pipe])) { Chris@0: unlink($this->files[$pipe]); Chris@0: } 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: $this->removeFiles(); 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@0: return array( Chris@0: array('pipe', 'r'), Chris@0: $nullstream, Chris@0: $nullstream, Chris@0: ); 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@0: return array( Chris@0: array('pipe', 'r'), Chris@0: array('file', 'NUL', 'w'), Chris@0: array('file', 'NUL', 'w'), Chris@0: ); 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@0: $read = $r = $e = array(); 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@0: $this->readBytes[$type] += strlen($data); Chris@0: $read[$type] = $data; Chris@0: } Chris@0: if ($close) { Chris@0: fclose($fileHandle); Chris@0: unset($this->fileHandles[$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@0: foreach ($this->fileHandles as $handle) { Chris@0: fclose($handle); Chris@0: } Chris@0: $this->fileHandles = array(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Removes temporary files. Chris@0: */ Chris@0: private function removeFiles() Chris@0: { Chris@0: foreach ($this->files as $filename) { Chris@0: if (file_exists($filename)) { Chris@0: @unlink($filename); Chris@0: } Chris@0: } Chris@0: $this->files = array(); Chris@0: } Chris@0: }